#6000. 「网络流 24 题」搭配飞行员
二分图匹配
#include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; inline int rd(){ int ret=0,f=1;char c; while(c=getchar(),!isdigit(c))f=c=='-'?-1:1; while(isdigit(c))ret=ret*10+c-'0',c=getchar(); return ret*f; } const int MAXN=128; const int M=1024<<1; const int INF=1<<30; struct Edge{ int next,to,f; }e[M<<1]; int ecnt=1,head[MAXN]; inline void add(int x,int y,int f){ e[++ecnt].f = f; e[ecnt].to = y; e[ecnt].next = head[x]; head[x] = ecnt; } int dep[MAXN]; queue<int> Q; bool bfs(int s,int t){ memset(dep,0,sizeof(dep)); Q.push(s);dep[s]=1; while(!Q.empty()){ int top=Q.front();Q.pop(); for(int i=head[top];i;i=e[i].next){ int v=e[i].to; if(dep[v]||!e[i].f) continue; dep[v]=dep[top]+1;Q.push(v); } } return dep[t]; } int cur[MAXN]; int dfs(int x,int flow,int t){ if(x==t) return flow; int used=0,tmp; for(int i=cur[x];i;i=e[i].next){ int v=e[i].to; if(dep[v]!=dep[x]+1) continue; tmp=dfs(v,min(flow-used,e[i].f),t); e[i].f-=tmp;e[i^1].f+=tmp;used+=tmp; if(used==flow) return flow; if(e[i].f) cur[x]=i; } if(!used) dep[x]=-1; return used; } int dinic(int s,int t){ int ret=0; while(bfs(s,t)){ memcpy(cur,head,sizeof(head)); ret+=dfs(s,INF,t); } return ret; } int n,m; int S,T; int main(){ n=rd();m=rd(); S=n+1;T=n+2; int x,y; while(~scanf("%d %d",&x,&y)){ add(x,y,1),add(y,x,0); } for(int i=1;i<=m;i++) add(S,i,1),add(i,S,0); for(int i=m+1;i<=n;i++) add(i,T,1),add(T,i,0); cout<<dinic(S,T); return 0; }
#6001. 「网络流 24 题」太空飞行计划
最大权闭合子图
在图G(V,E)中,选取G的子图T,T中每条边所指的点都在T中,称T为一个闭合图。
在所有闭合图中,集合中点的权值之和最大的子图为最大权闭合子图。
最大权闭合子图可以解决一类有依赖的点集最佳选择问题,可从A向B连一条有向边表示选A则必选B。
接下来构造一个网络,把点分为点权为正的点集A和点权为负的点集B。
建立源汇点,∀v∈A,从S向v连接容量为val[v]的边;∀v∈B,从v向T连接容量为-val[v]的边,原来的边连接正无穷的容量。
引入最小割求解,由于中间的边正无穷不可能割掉,所以要不割左边,要不割右边,即每个点会被割掉到S或到T的一条边。
最大权闭合子图的权值=正权之和-最小割。
这个题的读入真的很恶心
#include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; inline int rd(){ int ret=0,f=1;char c; while(c=getchar(),!isdigit(c))f=c=='-'?-1:1; while(isdigit(c))ret=ret*10+c-'0',c=getchar(); return ret*f; } const int MAXN=128; const int M=1024<<1; const int INF=1<<30; bool vis[MAXN]; struct Edge{ int next,to,f; }e[M<<1]; int ecnt=1,head[MAXN]; inline void add(int x,int y,int f){ e[++ecnt].f = f; e[ecnt].to = y; e[ecnt].next = head[x]; head[x] = ecnt; } int dep[MAXN]; queue<int> Q; bool bfs(int s,int t){ memset(vis,0,sizeof(vis)); memset(dep,0,sizeof(dep)); Q.push(s);dep[s]=1; while(!Q.empty()){ int top=Q.front();Q.pop();vis[top]=1; for(int i=head[top];i;i=e[i].next){ int v=e[i].to; if(dep[v]||!e[i].f) continue; dep[v]=dep[top]+1;Q.push(v); } } return dep[t]; } int cur[MAXN]; int dfs(int x,int flow,int t){ if(x==t) return flow; int used=0,tmp; for(int i=cur[x];i;i=e[i].next){ int v=e[i].to; if(dep[v]!=dep[x]+1) continue; tmp=dfs(v,min(flow-used,e[i].f),t); e[i].f-=tmp;e[i^1].f+=tmp;used+=tmp; if(used==flow) return flow; if(e[i].f) cur[x]=i; } if(!used) dep[x]=-1; return used; } int dinic(int s,int t){ int ret=0; while(bfs(s,t)){ memcpy(cur,head,sizeof(head)); ret+=dfs(s,INF,t); } return ret; } int n,m; int S,T; int main(){ m=rd();n=rd(); int x,y,sum=0; S=n+m+1;T=n+m+2; for(int i=1;i<=m;i++){ x=rd();sum+=x; add(S,i+n,x);add(i+n,S,0); while(1){ char c;int tmp=0,f=0; while(c=getchar(),c==' '); if(!isdigit(c)&&c!=' ') break; while(isdigit(c)&&!f)tmp=tmp*10+c-'0',c=getchar(); add(i+n,tmp,INF);add(tmp,i+n,0); } } for(int i=1;i<=n;i++){ x=rd(); add(i,T,x);add(T,i,0); } int ans=dinic(S,T); for(int i=n+1;i<=n+m;i++) if(vis[i]) printf("%d ",i-n); puts(""); for(int i=1;i<=n;i++) if(vis[i]) printf("%d ",i); puts(""); printf("%d",sum-ans); return 0; }
#6002. 「网络流 24 题」最小路径覆盖
拆点为入点和出点,对于一条边x,y,从x入点向y出点连容量为正无穷的边。
求最大流,理解为选择一对点,就会让总路径减一。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; inline int rd(){ int ret=0,f=1;char c; while(c=getchar(),!isdigit(c))f=c=='-'?-1:1; while(isdigit(c))ret=ret*10+c-'0',c=getchar(); return ret*f; } const int MAXN=256<<1; const int M=6000<<1; const int INF=1<<30; struct Edge{ int next,to,f; }e[M<<2]; int ecnt=1,head[MAXN]; inline void add(int x,int y,int f){ e[++ecnt].f = f; e[ecnt].to = y; e[ecnt].next = head[x]; head[x] = ecnt; } int dep[MAXN]; queue<int> Q; bool bfs(int s,int t){ memset(dep,0,sizeof(dep)); Q.push(s);dep[s]=1; while(!Q.empty()){ int top=Q.front();Q.pop(); for(int i=head[top];i;i=e[i].next){ int v=e[i].to; if(dep[v]||!e[i].f) continue; dep[v]=dep[top]+1;Q.push(v); } } return dep[t]; } int S,T; int n,m; int nxt[MAXN],lst[MAXN]; int cur[MAXN]; int dfs(int x,int flow,int t){ if(x==t) return flow; int used=0,tmp; for(int i=cur[x];i;i=e[i].next){ int v=e[i].to; if(dep[v]!=dep[x]+1) continue; tmp=dfs(v,min(flow-used,e[i].f),t); e[i].f-=tmp;e[i^1].f+=tmp;used+=tmp; if(tmp&&x!=S&&v!=T) lst[x]=v-n,nxt[v-n]=x; if(used==flow) return flow; if(e[i].f) cur[x]=i; } if(!used) dep[x]=-1; return used; } int dinic(int s,int t){ int ret=0; while(bfs(s,t)){ memcpy(cur,head,sizeof(head)); ret+=dfs(s,INF,t); } return ret; } bool vis[MAXN]; int sta[MAXN],top; void print(int x){ while(x!=nxt[x]){ sta[++top]=x; vis[x]=1; x=nxt[x]; } sta[++top]=x; vis[x]=1; while(top){ printf("%d ",sta[top--]); } } bool lk[MAXN<<1]; int main(){ n=rd();m=rd(); S=n+n+1;T=n+n+2; int x,y; for(int i=1;i<=n;i++) nxt[i]=lst[i]=i; for(int i=1;i<=m;i++){ x=rd();y=rd(); add(x,y+n,INF);add(y+n,x,0); } for(int i=1;i<=n;i++){ add(S,i,1),add(i,S,0); add(i+n,T,1);add(T,i+n,0); } int tmp=dinic(S,T); for(int i=1;i<=n;i++) if(lst[i]==i&&!vis[i]) print(i),puts(""); cout<<n-tmp; return 0; }
#6226. 「网络流 24 题」骑士共存问题
一个点向可以攻击到的点连容量为1的边(两点均合法),每一条从源点到汇点的弧表示有两个点会相互攻击。
计算出图的最小割,它的实际意义就是在所有没有障碍的点中至少需要删除多少个才能使得没有点可以相互攻击。
所以答案就是总可用点数n*n-m减去最小割。
注意一个问题,这样直接跑会把一个互相攻击的点对(a,b)建a->b和b->a的边,算了两次,答案应该除以2.
但是,这样会TLE。
考虑这样一个事情,棋盘黑白染色后,黑点只能攻击白点,反之亦然。
所以可以一种颜色的点连S,另一种连T,时间除以了2,答案就不需要除以2了。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; inline int rd(){ int ret=0,f=1;char c; while(c=getchar(),!isdigit(c))f=c=='-'?-1:1; while(isdigit(c))ret=ret*10+c-'0',c=getchar(); return ret*f; } const int MAXN=256,M=256*256*8; struct Edge{ int next,to,f; }e[M<<1]; int ecnt=1,head[MAXN*MAXN*2]; inline void add(int x,int y,int f){ e[++ecnt].f = f; e[ecnt].next = head[x]; e[ecnt].to = y; head[x] = ecnt; } queue<int> Q; int dep[MAXN*MAXN*2]; bool bfs(int s,int t){ memset(dep,0,sizeof(dep)); Q.push(s);dep[s]=1; while(!Q.empty()){ int top=Q.front();Q.pop(); for(int i=head[top];i;i=e[i].next){ int v=e[i].to; if(dep[v]||!e[i].f) continue; dep[v]=dep[top]+1;Q.push(v); } } return dep[t]; } int cur[MAXN*MAXN*2]; int dfs(int x,int flow,int t){ if(x==t) return flow; int tmp,used=0; for(int i=cur[x];i;i=e[i].next){ int v=e[i].to; if(dep[v]!=dep[x]+1) continue; tmp=dfs(v,min(flow-used,e[i].f),t); used+=tmp;e[i].f-=tmp;e[i^1].f+=tmp; if(used==flow) return flow; if(e[i].f) cur[x]=i; } if(!used) dep[x]=-1; return used; } int dinic(int s,int t){ int ret=0; while(bfs(s,t)){ memcpy(cur,head,sizeof(head)); ret+=dfs(s,1<<30,t); } return ret; } int n,m; bool bad[MAXN][MAXN]; const int dx[9]={0,-2,-1,1,2,2,1,-1,-2}; const int dy[9]={0,1,2,2,1,-1,-2,-2,-1}; #define id(x,y) (((x)-1)*(n)+(y)) int main(){ // freopen("testdata.txt","r",stdin); n=rd();m=rd(); int x,y,S,T,off; off=n*n;S=off*2+1;T=off*2+2; for(register int i=1;i<=m;i++){ x=rd();y=rd(); bad[x][y]=1; } for(register int i=1;i<=n;i++){ for(register int j=1;j<=n;j++){ if(bad[i][j]) continue; for(register int k=1;k<=8;k++){ x=i+dx[k];y=j+dy[k]; if(x<=0||x>n||y<=0||y>n) continue; if(bad[x][y])continue; add(id(i,j),off+id(x,y),1); add(off+id(x,y),id(i,j),0); } } } for(register int i=1;i<=n;i++){ for(register int j=1;j<=n;j++){ if((i+j)&1){ add(S,id(i,j),1); add(id(i,j),S,0); }else{ add(off+id(i,j),T,1); add(T,off+id(i,j),0); } } } cout<<n*n-m-dinic(S,T); return 0; }