10.网络流
网络流
开题顺序: \(BEFKQCRDGPAHT\)
\(A\) luogu P3511 [POI 2010] MOS-Bridges
-
考虑二分答案,现在需要解决的问题是将某些双向边进行定向。
-
入度和出度的和是固定的,考虑每条边对端点入度的改变量跑最大流判是否能满流即可。
-
最后根据满流的边跑一遍求解欧拉回路。
点击查看代码
const int inf=0x3f3f3f3f; int u[2010],v[2010],w[2][2010],du[2010],cur[2010],vis[2010]; vector<pair<int,int> >e[2010]; stack<int>s; struct MaxFlow { struct node { int nxt,to,cap,flow; }e[40010]; int head[4010],dis[4010],vis[4010],cur[4010],cnt=1; void clear() { memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); cnt=1; } void add(int u,int v,int w) { cnt++; e[cnt]=(node){head[u],v,w,0}; head[u]=cnt; cnt++; e[cnt]=(node){head[v],u,0,0}; head[v]=cnt; } bool bfs(int s,int t) { memset(vis,0,sizeof(vis)); queue<int>q; dis[s]=1; cur[s]=head[s]; q.push(s); vis[s]=1; while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0&&e[i].cap>e[i].flow) { dis[e[i].to]=dis[x]+1; cur[e[i].to]=head[e[i].to]; q.push(e[i].to); vis[e[i].to]=1; if(e[i].to==t) return true; } } } return false; } int dfs(int x,int t,int flow) { if(x==t) return flow; int sum=0,tmp; for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt) { cur[x]=i; if(dis[e[i].to]==dis[x]+1&&e[i].cap>e[i].flow) { tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum)); if(tmp==0) dis[e[i].to]=0; sum+=tmp; e[i].flow+=tmp; e[i^1].flow-=tmp; } } return sum; } int Dinic(int s,int t) { int flow=0; while(bfs(s,t)==true) flow+=dfs(s,t,inf); return flow; } }F; bool check(int n,int m,int mid,int sum) { F.clear(); int s=n+m+1,t=n+m+2; for(int i=1;i<=m;i++) { F.add(s,n+i,1); if(w[0][i]<=mid) F.add(n+i,v[i],1); if(w[1][i]<=mid) F.add(n+i,u[i],1); } for(int i=1;i<=n;i++) F.add(i,t,du[i]/2); return F.Dinic(s,t)>=sum; } void dfs(int x,int fa) { for(int i=cur[x];i<e[x].size();i=max(i+1,cur[x])) { if(vis[e[x][i].second]==0) { vis[e[x][i].second]=1; cur[x]=i+1; dfs(e[x][i].first,e[x][i].second); } } if(fa!=0) s.push(fa); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l=0,r=1000,mid,ans=0,flag=1,sum=0,i,j; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u[i]>>v[i]>>w[0][i]>>w[1][i]; du[u[i]]++; du[v[i]]++; l=max(l,min(w[0][i],w[1][i])); } for(i=1;i<=n;i++) { flag&=(du[i]%2==0); sum+=du[i]/2; } if(flag==0) cout<<"NIE"<<endl; else { while(l<=r) { mid=(l+r)/2; if(check(n,m,mid,sum)==true) { ans=mid; r=mid-1; } else { l=mid+1; } } cout<<ans<<endl; check(n,m,ans,sum); for(i=1;i<=m;i++) { for(j=F.head[n+i];j!=0;j=F.e[j].nxt) { if(F.e[j].cap==F.e[j].flow) { if(F.e[j].to==v[i]) e[u[i]].push_back(make_pair(v[i],i)); else e[v[i]].push_back(make_pair(u[i],i)); } } } dfs(1,0); while(s.empty()==0) { cout<<s.top()<<" "; s.pop(); } } return 0; }
\(B\) luogu P2517 [HAOI2010] 订货
\(C\) luogu P3358 最长k可重区间集问题
\(D\) luogu P4001 [ICPC-Beijing 2006] 狼抓兔子
\(E\) luogu P4897 【模板】最小割树(Gomory-Hu Tree)
\(F\) luogu P4043 [AHOI2014/JSOI2014] 支线剧情
\(G\) UVA1306 The K-League
-
考虑每两场队伍之间的比赛向两个队伍之间连边来分配胜负,通过判断最大流是否等于总比赛数得到是否合法。
-
特判无论如何都不能获胜的情况。
点击查看代码
const int inf=0x3f3f3f3f; int w[50],a[50][50],sum[50]; vector<int>ans; struct MaxFlow { struct node { int nxt,to,cap,flow; }e[12010]; int head[1510],dis[1510],vis[1510],cur[1510],cnt=1; void clear() { memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); cnt=1; } void add(int u,int v,int w) { cnt++; e[cnt]=(node){head[u],v,w,0}; head[u]=cnt; cnt++; e[cnt]=(node){head[v],u,0,0}; head[v]=cnt; } bool bfs(int s,int t) { memset(vis,0,sizeof(vis)); queue<int>q; dis[s]=1; cur[s]=head[s]; q.push(s); vis[s]=1; while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0&&e[i].cap>e[i].flow) { dis[e[i].to]=dis[x]+1; cur[e[i].to]=head[e[i].to]; q.push(e[i].to); vis[e[i].to]=1; if(e[i].to==t) return true; } } } return false; } int dfs(int x,int t,int flow) { if(x==t) return flow; int sum=0,tmp; for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt) { cur[x]=i; if(dis[e[i].to]==dis[x]+1&&e[i].cap>e[i].flow) { tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum)); if(tmp==0) dis[e[i].to]=0; sum+=tmp; e[i].flow+=tmp; e[i^1].flow-=tmp; } } return sum; } int Dinic(int s,int t) { int flow=0; while(bfs(s,t)==true) flow+=dfs(s,t,inf); return flow; } }F; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int tesecase,n,s,t,x,num,flag,i,j,k; cin>>tesecase; for(;tesecase>=1;tesecase--) { ans.clear(); cin>>n; num=0; s=n*n+n+1; t=n*n+n+2; for(i=1;i<=n;i++) cin>>w[i]>>x; for(i=1;i<=n;i++) { sum[i]=w[i]; for(j=1;j<=n;j++) { cin>>a[i][j]; num+=a[i][j]; sum[i]+=a[i][j]; } } num/=2; for(k=1;k<=n;k++) { F.clear(); flag=1; for(i=1;i<=n;i++) { for(j=i+1;j<=n;j++) { F.add(s,(i-1)*n+j,a[i][j]); F.add((i-1)*n+j,n*n+i,a[i][j]); F.add((i-1)*n+j,n*n+j,a[i][j]); } F.add(n*n+i,t,sum[k]-w[i]); flag&=(sum[k]-w[i]>=0); } if(flag==1&&F.Dinic(s,t)==num) ans.push_back(k); } for(i=0;i<ans.size();i++) { cout<<ans[i]; if(i!=ans.size()-1) cout<<" "; } cout<<endl; } return 0; }
\(H\) luogu P5458 [BJOI2016] 水晶
-
将三维坐标 \((x,y,z)\) 转换成 \((x-z,y-z)\) 。
-
两种共振本质相同,都可以写成三个连通的单元使得横纵坐标和 \(\bmod 3\) 后 \(=0,1,2\) 各一次,其中 \(\bmod 3=0\) 的有能量源。
-
拆点并建出三分图后跑最小割即可。
点击查看代码
const int inf=0x3f3f3f3f,dx[6]={1,0,-1,0,-1,1},dy[6]={0,-1,0,1,-1,1}; map<pair<int,int>,int>c,id; map<pair<int,int>,int>::iterator it; struct MinCut { struct node { int nxt,to,cap,flow; }e[800010]; int head[100010],dis[100010],vis[100010],cur[100010],cnt=1; void add(int u,int v,int w) { cnt++; e[cnt]=(node){head[u],v,w,0}; head[u]=cnt; cnt++; e[cnt]=(node){head[v],u,0,0}; head[v]=cnt; } bool bfs(int s,int t) { memset(vis,0,sizeof(vis)); queue<int>q; dis[s]=1; cur[s]=head[s]; q.push(s); vis[s]=1; while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0&&e[i].cap>e[i].flow) { dis[e[i].to]=dis[x]+1; cur[e[i].to]=head[e[i].to]; q.push(e[i].to); vis[e[i].to]=1; if(e[i].to==t) return true; } } } return false; } int dfs(int x,int t,int flow) { if(x==t) return flow; int sum=0,tmp; for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt) { cur[x]=i; if(dis[e[i].to]==dis[x]+1&&e[i].cap>e[i].flow) { tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum)); if(tmp==0) dis[e[i].to]=0; sum+=tmp; e[i].flow+=tmp; e[i^1].flow-=tmp; } } return sum; } int Dinic(int s,int t) { int flow=0; while(bfs(s,t)==true) flow+=dfs(s,t,inf); return flow; } }C; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,s,t,tot,x,y,z,_x,_y,w,sum=0,i; cin>>n; s=1; tot=t=2; for(i=1;i<=n;i++) { cin>>x>>y>>z>>w; x-=z; y-=z; w*=(10+(((x+y)%3+3)%3==0)); c[make_pair(x,y)]+=w; sum+=w; } for(it=c.begin();it!=c.end();it++) { tot++; id[it->first]=tot; tot++; C.add(id[it->first],tot,it->second); } for(it=c.begin();it!=c.end();it++) { x=it->first.first; y=it->first.second; if(((x+y)%3+3)%3==1) C.add(id[it->first]+1,t,inf); else { if(((x+y)%3+3)%3==2) C.add(s,id[it->first],inf); for(i=0;i<=5;i++) { _x=x+dx[i]; _y=y+dy[i]; if(c.find(make_pair(_x,_y))!=c.end()&&((x+y)%3+3+1)%3==((_x+_y)%3+3)%3) C.add(id[it->first]+1,id[make_pair(_x,_y)],inf); } } } printf("%.1lf\n",(sum-C.Dinic(s,t))/10.0); return 0; }
\(I\) CF724E Goods transportation
\(J\) CF1288F Red-Blue Graph
\(K\) luogu P2805 [NOI2009] 植物大战僵尸
\(L\) CF1404E Bricks
\(M\) luogu P8291 [省选联考 2022] 学术社区
\(N\) luogu P4076 [SDOI2016] 墙上的句子
\(O\) luogu P5470 [NOI2019] 序列
\(P\) luogu P4066 [SHOI2003] 吃豆豆
-
两条路径不能互相穿过很诈骗。拆成入点和出点后从源点找 \(2\) 条路径即可。
-
需要精细实现一下连边。具体地,对于形如 \(i \to j \to k\) 的路径,我们只保留 \(i \to j,j \to k\) 两条路径而不保留 \(i \to k\) ,即只与右侧必须要走的点连边。
点击查看代码
const int inf=0x3f3f3f3f; pair<int,int>a[2010]; struct MaxFlowMaxCost { struct node { int nxt,to,w,cap,flow; }e[750010]; int head[4010],dis[4010],vis[4010],cur[4010],cnt=1,cost; void add(int u,int v,int w,int _w) { cnt++; e[cnt]=(node){head[u],v,_w,w,0}; head[u]=cnt; cnt++; e[cnt]=(node){head[v],u,-_w,0,0}; head[v]=cnt; } bool spfa(int s,int t) { memset(vis,0,sizeof(vis)); memset(dis,-0x3f,sizeof(dis)); queue<int>q; dis[s]=0; cur[s]=head[s]; q.push(s); vis[s]=1; while(q.empty()==0) { int x=q.front(); q.pop(); vis[x]=0; for(int i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]<dis[x]+e[i].w&&e[i].cap>e[i].flow) { dis[e[i].to]=dis[x]+e[i].w; cur[e[i].to]=head[e[i].to]; if(vis[e[i].to]==0) { q.push(e[i].to); vis[e[i].to]=1; } } } } return dis[t]>0; } int dfs(int x,int t,int flow) { if(x==t) return flow; vis[x]=1; int sum=0,tmp; for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt) { cur[x]=i; if(vis[e[i].to]==0&&dis[e[i].to]==dis[x]+e[i].w&&e[i].cap>e[i].flow) { tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum)); sum+=tmp; e[i].flow+=tmp; e[i^1].flow-=tmp; cost+=tmp*e[i].w; } } vis[x]=0; return sum; } int Dinic(int s,int t) { cost=0; while(spfa(s,t)==true) while(dfs(s,t,inf)!=0); return cost; } }C; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,minn,s,_s,t,i,j; cin>>n; _s=2*n+1; s=2*n+2; t=2*n+3; for(i=1;i<=n;i++) cin>>a[i].first>>a[i].second; sort(a+1,a+1+n); for(i=1;i<=n;i++) { C.add(s,i,inf,0); C.add(i+n,t,inf,0); C.add(i,i+n,1,1); C.add(i,i+n,2,0); minn=inf; for(j=i+1;j<=n;j++) { if(a[j].second>=a[i].second&&a[j].second<minn) { C.add(i+n,j,2,0); minn=a[j].second; } } } C.add(_s,s,2,0); cout<<C.Dinic(_s,t)<<endl; return 0; }
\(Q\) luogu P4553 80人环游世界
\(R\) luogu P3980 [NOI2008] 志愿者招募
\(S\) luogu P6122 [NEERC 2016] Mole Tunnels
\(T\) luogu P5331 [SNOI2019] 通信
-
将一个点 \(i\) 拆成两个点 \(i,i'\) ,分别用于直接连接到控制中心和被后面某个哨站连接。
-
从 \(s\) 向 \(i\) 连一条 \(c=1,w=0\) 的边,从 \(i\) 向 \(t\) 连一条 \(c=1,w=W\) 的边,从 \(i'\) 向 \(t\) 连一条 \(c=1,w=0\) 的边,从 \(i\) 向 \(j'(j<i)\) 连一条 \(c=1,w=|a_{i}-a_{j}|\) 的边。取到最大流时合法。
-
暴力连边的边数是 \(O(n^{2})\) 的,需要进一步优化。
-
考虑分治优化建图,具体地,将 \([l,r]\) 内的 \(a_{i}\) 升序排序后得到一条虚链, \([mid+1,r]\) 内的点向虚链连边,虚链上的点向 \([l,mid]\) 内的点连边,此时边数缩小到了 \(O(n \log n)\) ,可以接受。
点击查看代码
const ll inf=0x3f3f3f3f3f3f3f3f; ll a[1010],b[1010],id[2][1010],s,t,tot; struct MaxFlowMinCost { struct node { ll nxt,to,w,cap,flow; }e[5000010]; ll head[13010],dis[13010],vis[13010],cur[13010],cnt=1,cost; void add(ll u,ll v,ll w,ll _w) { cnt++; e[cnt]=(node){head[u],v,_w,w,0}; head[u]=cnt; cnt++; e[cnt]=(node){head[v],u,-_w,0,0}; head[v]=cnt; } bool bfs(ll s,ll t) { memset(vis,0,sizeof(vis)); memset(dis,0x3f,sizeof(dis)); queue<ll>q; dis[s]=0; cur[s]=head[s]; q.push(s); vis[s]=1; while(q.empty()==0) { ll x=q.front(); q.pop(); vis[x]=0; for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w&&e[i].cap>e[i].flow) { dis[e[i].to]=dis[x]+e[i].w; cur[e[i].to]=head[e[i].to]; q.push(e[i].to); vis[e[i].to]=1; } } } return dis[t]<inf; } ll dfs(ll x,ll t,ll flow) { if(x==t) return flow; vis[x]=1; ll sum=0,tmp; for(ll i=cur[x];i!=0&&sum<flow;i=e[i].nxt) { cur[x]=i; if(vis[e[i].to]==0&&dis[e[i].to]==dis[x]+e[i].w&&e[i].cap>e[i].flow) { tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum)); sum+=tmp; e[i].flow+=tmp; e[i^1].flow-=tmp; cost+=tmp*e[i].w; } } vis[x]=0; return sum; } ll Dinic(ll s,ll t) { cost=0; while(bfs(s,t)==true) while(dfs(s,t,inf)!=0); return cost; } }F; void solve(ll l,ll r) { if(l==r) return; ll mid=(l+r)/2; solve(l,mid); solve(mid+1,r); b[0]=0; for(ll i=l;i<=r;i++) { b[0]++; b[b[0]]=a[i]; } sort(b+1,b+1+b[0]); b[0]=unique(b+1,b+1+b[0])-(b+1); for(ll i=1;i<=b[0]-1;i++) { F.add(tot+i,tot+i+1,inf,b[i+1]-b[i]); F.add(tot+i+1,tot+i,inf,b[i+1]-b[i]); } for(ll i=l;i<=r;i++) { ll pos=lower_bound(b+1,b+1+b[0],a[i])-b; if(i<=mid) F.add(tot+pos,id[1][i],1,0); else F.add(id[0][i],tot+pos,1,0); } tot+=b[0]; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,w,i; cin>>n>>w; s=1; tot=t=2; for(i=1;i<=n;i++) { cin>>a[i]; tot++; id[0][i]=tot; tot++; id[1][i]=tot; F.add(s,id[0][i],1,0); F.add(id[0][i],t,1,w); F.add(id[1][i],t,1,0); } solve(1,n); cout<<F.Dinic(s,t)<<endl; return 0; }
\(U\) luogu P6621 [省选联考 2020 A 卷] 魔法商店
\(V\) luogu P2304 [NOI2015] 小园丁与老司机
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18707030,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。