uva 563 Crimewave

网络流最大流(经典的逃脱问题但是不会做啊)

这题不会做,问了别人的思路,知道是最大流,但是还是不会编码,后来找了解题报告,看了建图部分明白了,然后就是最大流EK算法模板即可

算法思路:对于给定的网格,行为S列为A,我们按行优先给所有点标号,从1到S*A。然后对每个点拆点,拆点后一个点变为两个点(那么总点数为2*S*A),在这里我把拆点后的两个点叫做“前点”和“后点”,对于坐标为(i,j)的点,拆点后“前点”的编号为u=(i-1)*A+j , “后点”的编号好v=u+S*A;

我们还要额外设置一个源点s,编号为0,一个汇点,编号为2*S*A+1。从源点s建有向边指向所有的银行,从所有网格边沿的点建有向边指向汇点t,另外网格内一个点要和它上下左右四个点建立无向边(也就是两条有向边)。数据很大,要用邻接表保存

 

问题就是,我们已经事先拆点了,原来的两个点(i,j)和(i,j+1)有连线那么拆点后怎么链接呢?

第一部分(一个点和它上下左右的四个点建边):从这个点的“后点”和四周的点的“前点”建有向边(因为用邻接表建图,所以所有的有向边都有反边,注意反边的容量为0)。

第二部分(源点和所有银行建边):源点s和所有银行的“前点”建有向边(还有反边)

第三部分(所有网格边沿的点和汇点建边):所有网格边沿的点的“后点”和汇点建有向边(还有反边)

因此

第一部分:两个点a,b之间会有四边有向边,一条是a点的“后点”指向b点的“前点”,一条是b点的“后点”指向a点的“前点”。这两条还附带两条反边所以一共四条

第二部分:源点和银行的“前点”有边,再附带一个反边

第三部分:网格边沿点的“后点”和汇点有边,再附带一个反边

所有边的容量都1(这样就起到了每个点只能用一次的效果),附带的反边容量当然是为0

建图后,直接EK,算最大流,最大流等于银行个数那么可以逃脱,不等(小于)就不可以

 

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define N 5500  //拆点后的个数
#define M 30100  //边的数目
#define INF 0x3f3f3f3f
struct edge
{
    int u,v,f,cap,next;
    //边的顶点,流量,容量
}e[M];
int first[N],path[N];
int dx[5]={0,-1,1,0,0} , dy[5]={0,0,0,-1,1};  //上下左右的坐标
int S,A,B,n,s,t,F,edgenum;

void add(int u , int v , int cap)
{
    int t;
    e[edgenum].u=u; e[edgenum].v=v;
    e[edgenum].f=0; e[edgenum].cap=cap;
    e[edgenum].next=first[u];
    first[u]=edgenum;
    edgenum++;
    t=u; u=v; v=t;
    e[edgenum].u=u; e[edgenum].v=v;
    e[edgenum].f=0; e[edgenum].cap=0; //注意这里的cap为0
    e[edgenum].next=first[u];
    first[u]=edgenum;
    edgenum++;
    return ;
}

void EK()
{
    queue<int>q;
    int a[N];
    F=0;
    while(1)
    {
        memset(a,0,sizeof(a));
        memset(path,-1,sizeof(path));
        a[s]=INF;
        q.push(s);
        while(!q.empty())
        {
            int u;
            u=q.front(); q.pop();
            for(int k=first[u]; k!=-1 ; k=e[k].next) //遍历点k的邻接表
            {
                int v=e[k].v;
                if(!a[v] && e[k].f<e[k].cap) 
                {
                    a[v]=a[u] < e[k].cap-e[k].f ? a[u]:e[k].cap-e[k].f;
                    //递推a数组
                    path[v]=k;  //记录路径注意邻接表是记录边的编号
                    q.push(v);
                }
            }
        }

        if(!a[t])  break;  //不可增广证明已经是最大流

        for(int k=path[t]; k!=-1; k=path[e[k].u])  //增广,注意路径不要搞错
        {
            //printf("%d<--%d ",e[k].v,e[k].u);
            e[k].f+=a[t];
            e[k^1].f-=a[t];
        }
        F+=a[t];
    }
    if(F==B)
        printf("possible\n");
    else
        printf("not possible\n");
    //printf("%d\n",F);
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&S,&A,&B);
        s=0;  t=S*A*2+1;  //设置源点和汇点
        memset(first,-1,sizeof(first));
        edgenum=0;  //记录最后邻接表中边的数目
        for(int i=1; i<=S; i++)
            for(int j=1; j<=A; j++) 
            {
                int u,v;
                u=(i-1)*A+j;   v=u+S*A;  // 前点,后点
                //拆成两点u,v并且建边
                add(u,v,1);  //建边u-->v,容量为1
                for(int k=1; k<=4; k++) //和他周围四个点相连
                {
                    int x=i+dx[k] , y=j+dy[k];  //计算四周和它相连的点的坐标
                    if(x>=1 && x<=S && y>=1 && y<=A)  //(x,y)不是网格边沿的点
                    {
                        int vv=(x-1)*A+y;  //(x,y)的“前点”
                        add(v,vv,1);  
                        //一个点的“后点”和一个点的“前点”相连
                    }
                    else   //(i,j)是网格边沿的点,那么和汇点相连
                        add(v,t,1);  //“后点”和汇点相连
                }
            }
        
        for(int k=1; k<=B; k++)  //读取所有银行的信息
        {
            int i,j,u;
            scanf("%d%d",&i,&j);
            u=(i-1)*A+j;  //"前点"
            add(s,u,1);
        }

        EK();  //最大流EK算法
    }
    return 0;
}

 

posted @ 2012-12-20 22:08  Titanium  阅读(905)  评论(4编辑  收藏  举报