网络流最大流(经典的逃脱问题但是不会做啊)
这题不会做,问了别人的思路,知道是最大流,但是还是不会编码,后来找了解题报告,看了建图部分明白了,然后就是最大流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; }