骑士共存问题 (二分图最大匹配 转换 网络最大流 )

来源:互联网 发布:unity3d 2d游戏教程 编辑:程序博客网 时间:2024/05/10 23:58

在一个n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘
上某些方格设置了障碍,骑士不得进入。

 

对于给定的n*n个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑
士,使得它们彼此互不攻击。

第一行有2 个正整数n 和m (1<=n<=200, 0<=m<n^2),
分别表示棋盘的大小和障碍数。接下来的m 行给出障碍的位置。每行2 个正整数,表示障
碍的方格坐标。

将计算出的共存骑士数输出

3 2

1 1

3 3

5

      图中 :  S能攻击到8个X的地方;

把棋盘染色,相邻的染不同色,因为对角线是攻击不到的,不是对角线的两个点,是有可能互相攻击到的, .;  

这样就能够把棋盘看成二分图,相邻的在不同集合,颜色一样的在一个集合 .  如 (i+j)%2==0 在一个集合, (i+j)%2!=0在一个集合 ;   然后就转换成求图的最大独立集(集合里任易两点不连边)  ;  二分图 最大独立集= 顶点数(出去障碍) - 最大匹配数  ;  但是这题数据大, 不能用匈牙利算法求最大匹配 ;  所以要转换成 网络最大流求最大匹配 ;   

 建图  :    加一个源点S , 让S与X集合的点连边 ,容量设为1 ; 

               加一个汇点T ,让Y集合的点连边到T ,容量也为1 ;

               X集合中的点,连接能攻击到的Y集合的中的点, 连接边 xi-->yi ;容量也为1 ;

最后用dinic算法求网络最大流, 就是最后的匹配数 ;

     第一次搞网络流 !!!!!!!!!!   终于破网络流的处了  ...  


#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
#define INF 99999999


int h[8]={-1,-2,-2,-1,1,2,2,1};
int l[8]={-2,-1,1,2,2,1,-1,-2};
bool can[201][201];
int maxflow=0;       //网络最大流


const int MAXN=200*200+2;
struct node
{
    int x,y,c;       //存边,c是边的可增容量,即在这条边能修改的流量. 
    int op;         //op是反向边.即残留网络中的,反向边.
    int next;
}p[MAXN*8*2+1];
int first[MAXN];
int level[MAXN],st[MAXN],t[MAXN],state[MAXN];
int count_=0;
void addedge(int a,int b,int c)   //加边,动态链表存储
{
    
    count_++;
    p[count_].x=a,p[count_].y=b,p[count_].c=c;
    p[count_].next=first[a];                     //指向上次一条a-->x,的边的结点
    p[count_].op=count_+1;first[a]=count_;     //op指向反向边 ,所以count+1 ;
    count_++;
    p[count_].x=b,p[count_].y=a,p[count_].c=0;  //初始化反向边为0 ;因为刚开始还没流量
    p[count_].next=first[b];
    p[count_].op=count_-1;first[b]=count_;  //op指向正向边,所以count-1 ;

                             //a-->b ,和 b -->a  ,在的结构体中的位置 通过op互相指向 .
}


int N,M;
int S,T;
bool make_level()    / /建层次网络
{
    
    int head,tail,i,j;
    memset(level,-1,sizeof(level));
    level[S]=0;              //初始化
    st[head=tail=0]=S;
    while(head<=tail)
    {
        i=st[head++];
        for (int v=first[i];v!=-1;v=p[v].next)   //从i点流出去,可能有不止一条流
        {
            
            j=p[v].y;
            if (p[v].c&&level[j]==-1)
            {
                level[j]=level[i]+1;     //赋值
                if (j==T) return true;   //层次网络构建完了
                st[++tail]=j;
            }
        }
    }
    return false;
}
void dinic()   / / 寻找增光路,并修改流量.
{
    int i,j,delta,Stop;
    memset(st,-1,sizeof(st));   //可有可无.因为下面会覆盖掉以前的值
    st[Stop=1]=S;         //初始化加入源点, 注意st[]是从st[1]开始的;
    for (int i=S;i<=T;i++)   //赋值 ; 可有可无
    {
        t[i]=first[i];
    }
    while(Stop)   
    {
        i=st[Stop];
        if (i!=T)     //还没到汇点
        {
            while(t[i]!=-1)    
            {                                 //从i-->j,找到一条边,这条在层次网络中 ;
                if (p[t[i]].c&&level[i]+1==level[j=p[t[i]].y]) break;  
                t[i]=p[t[i]].next;     //因为从i出去的边有很多,知道找到边,是在层次网络中的.
            }
            if (t[i]!=-1)    //找到了   
            {
                st[++Stop]=j;      //加入;
                state[Stop]=t[i];   //存这条边在的结构体的位置; 注意state[]是从2开始的;
            }
            else    //一条都找不到,回溯,继续找
            {
                Stop--;
                level[i]=-1;
            }
        }
        else     //如果到汇点了,说明找到了一条增光路
        {
            delta=INF;
            for (i=Stop;i>=2;i--)    //在这条增光路中找到可改变流量最小的,即c
                if (p[state[i]].c<delta) delta=p[state[i]].c;
            maxflow+=delta;     //得到一个修改流量
            for (i=Stop;i>=2;i--)   //调整层次网络
            {
                p[state[i]].c-=delta;  //可修改流量减少了delta
                int op_=p[state[i]].op;  //反向边
                p[op_].c+=delta;       //相应的,反向流量就增加了c
                if (p[state[i]].c==0)    Stop=i-1;     //如果修改完后,这条边已经没有流量可修改了,    
            }                                       //就删除这条边.退一步(Stop=i-1) ,继续下次找增光路;
        }              
    }
}
int main()
{
    memset(can,true,sizeof(can));
    memset(first,-1,sizeof(first));
    cin>>N>>M;
    for (int i=1;i<=M;i++)  //存障碍
    {
        int x,y;
       cin>>x>>y;
        can[x][y]=false;
    }
    
    S=0,T=N*N+1;   //初始化源点,汇点
    for (int i=1;i<=N;i++)
    {
        for (int j=1;j<=N;j++)
        {
            if (!can[i][j]) continue;  

             if ((i+j)%2==0) {addedge(((i-1)*N+j),T,1);continue;}   //y集合中的点,跟汇点连
            int id1=((i-1)*N+j);                  //(i-1)*N+j)是这个坐标,是第几个点;
            addedge(0,id1,1);         //X集合中的点,跟源点连
            for (int k=0;k<8;k++)
            {
                int x,y;
                x=i+h[k],y=j+l[k];      //攻击到y集合中的点
                if (x<1||y<1||x>N||y>N) continue;
                   if (!can[x][y]) continue;
                   int id2=((x-1)*N+y);
                   addedge(id1,id2,1);     //X集合中的点和y集合中点,连边
            }
        }
    }
    
    while(make_level())  //建层次网络,找一次,修改, 直到无法建层次网络了 ;
        dinic();


    cout<<N*N-M-maxflow<<endl;   //格子数-最大匹配数 ;
    getchar();
getchar(); 
    return 0;
}



原创粉丝点击