网络流24题
1.餐巾计划问题
这道题目算这些题目里比较难的题目,详细的说一下
首先我们注意到每天要求的纸巾不同,那很显然最后流入汇点一定是分别流入的
考虑拆点
按照一般的思路我们从超级源向早上连边表示提供新的纸巾
接下来晚上流入汇点是否可以呢
我们考虑一下我们晚上是要有向某一天的早上(快洗慢洗)连边的操作的
这会导致进入汇点的流量不对
所以我们改变一下
早上向汇点连边,表示当天获得了xx条新纸巾
而源点向晚上连边,表示可以获得xx条旧纸巾
网上的题解写的是前一天晚上向下一天晚上连边,然后源点向每天早上连边
其实我们可以每天早上向下一天早上连边,然后从起点向1早上连INF,这样可以减少边数
另外晚上向慢洗快洗好的那天连边
#include <bits/stdc++.h> using namespace std; #define ll long long #define rint register ll #define IL inline #define rep(i,h,t) for (rint i=h;i<=t;i++) #define dep(i,t,h) for (rint i=t;i>=h;i--) #define me(x) memset(x,0,sizeof(x)) const ll N=5000; const ll N2=N*20; const ll INF=1e9; bool inq[N]; ll head[N],d[N],p[N],aa[N],n,m,s,t,l,f[N]; struct re{ ll a,b,c,flow,cost,from; }a[N2]; void arr(ll x,ll y,ll z,ll cost) { a[++l].a=head[x]; a[l].b=y; a[l].c=z; a[l].cost=cost; a[l].from=x; head[x]=l; } ll flow,cost; struct Dinic{ ll n; bool bf() { rep(i,1,n) d[i]=INF; me(inq); p[s]=0; aa[s]=INF; queue<ll> q; q.push(s); while (!q.empty()) { ll x=q.front(); q.pop(); inq[x]=0; for (rint u=head[x];u;u=a[u].a) { ll v=a[u].b; if (a[u].c>a[u].flow&&d[v]>d[x]+a[u].cost) { d[v]=d[x]+a[u].cost; p[v]=u; aa[v]=min(aa[x],a[u].c-a[u].flow); if (!inq[v]) { q.push(v); inq[v]=1; } } } } if (d[t]==INF) return(0); flow+=aa[t]; cost+=d[t]*aa[t]; ll x=t; while (x!=s) { ll u=p[x]; a[u].flow+=aa[t]; if (u%2) a[u+1].flow-=aa[t]; else a[u-1].flow-=aa[t]; x=a[u].from; } return 1; } void mincost() { while (bf()); } }D; ll pp,ff,nn,ss; IL void arr2(ll x,ll y,ll z,ll cost) { arr(x,y,z,cost); arr(y,x,0,-cost); } int main() { freopen("1.in","r",stdin); freopen("1.out","w",stdout); ios::sync_with_stdio(false); cin>>n; rep(i,1,n) cin>>f[i]; s=0; t=2*n+1; cin>>pp>>m>>ff>>nn>>ss; arr2(s,1,INF,pp); rep(i,1,n) { arr2(i,t,f[i],0); if (i!=n) arr2(i,i+1,INF,0); arr2(s,i+n,f[i],0); if (i+nn<=n) arr2(i+n,i+nn,INF,ss); if (i+m<=n) arr2(i+n,i+m,INF,ff); } D.n=n*2+1; D.mincost(); cout<<cost<<endl; return 0; }
2. [CTSC1999]家园
分层图跑最大流,还是比较简单的
3. 飞行员配对方案问题
裸题
4.软件补丁问题
并不是网络流的题目
状压dp,由于转移存在环跑spfa
5.太空飞行计划问题
最小割裸题
6.试题库问题
裸题
7. 最小路径覆盖问题
二分图经典题目,前面的整理提到过了
将每个点拆成入点和出点,跑最大流
答案=点数-最大流
8.魔术球问题
这道题还是要写一下的
首先从小到大枚举a和第二题是一样的,这样子的效率是高于二分答案的
然后问题就变成了最小路径覆盖
当最小路径覆盖大于它的时候,答案就是a-1
然后输出路径的时候
我们可以直接利用当前图,因为第n+1个一定没有在前面的环中放上
然后我们查找到每个点的后一个点是什么
注意多个环的话终点是t,一个环的话没有被更新终点是0
#include <bits/stdc++.h> using namespace std; #define rint register int #define IL inline #define rep(i,h,t) for (rint i=h;i<=t;i++) #define dep(i,t,h) for (rint i=t;i>=h;i--) #define me(x) memset(x,0,sizeof(x)) const int N=2e4; const int N2=1e5; const int INF=1e9; int s,t,n,m,flow,l,nxt[N]; bool f[N]; struct re{ int a,b,c,flow,x1,y1; }a[N2]; struct Di{ bool vis[N]; int head[N],d[N]; void arr(int x,int y,int z,int x1,int y1) { a[++l].a=head[x]; a[l].b=y; a[l].c=z; a[l].x1=x1; a[l].y1=y1; head[x]=l; } bool bfs() { me(vis); queue<int> q; q.push(s); d[s]=0; vis[s]=1; while(!q.empty()) { rint x=q.front(); q.pop(); for (rint u=head[x];u;u=a[u].a) { rint v=a[u].b; if (!vis[v]&&a[u].c>a[u].flow) { vis[v]=1; q.push(v); d[v]=d[x]+1; } } } return(vis[t]); } int dfs(int x,int y) { if (x==t||!y) return(y); int ans=0,f; for (rint u=head[x];u;u=a[u].a) { rint v=a[u].b; if (d[v]==d[x]+1&&(f=dfs(v,min(a[u].c-a[u].flow,y)))>0) { ans+=f; a[u].flow+=f; if (u%2) a[u+1].flow-=f; else a[u-1].flow-=f; y-=f; if (!y) break; } } return(ans); } void maxflow() { while(bfs()) flow+=dfs(s,INF); } }D; #define arr2(x,y,z,a,b) D.arr(x,y,z,a,b),D.arr(y,x,0,b,a) int main() { freopen("1.in","r",stdin); freopen("1.out","w",stdout); ios::sync_with_stdio(false); s=0; t=10000; cin>>n; rep(i,1,10000) { int x1=i*2-1,x2=i*2; arr2(s,x1,1,s,i); arr2(x2,t,1,i,t); rep(j,1,i-1) { int tmp=sqrt(i+j); if (tmp*tmp==i+j) { arr2(j*2-1,x2,1,j,i); } } D.maxflow(); if (i-flow>n) { rep(i,1,l) if (a[i].c==a[i].flow&&a[i].c) { nxt[a[i].x1]=a[i].y1; int k1; k1++; } cout<<i-1<<endl; rep(j,1,i-1) { int tt=j; if (!f[j]) { while (j!=t&&j) { f[j]=1; cout<<j<<" "; j=nxt[j]; } cout<<endl; } j=tt; } exit(0); } } return 0; }
10.航空路线问题
网络流的话是裸题,两个路径求最大经过点数,显然费用流
同时是dp经典题目
有向无环图两条并行路径dp
dp[i][j]转移的时候只能向max(i,j)转移
为什么呢 因为这样既可以保证每个可行状态都可到达
又能保证每个只会被算一次
#include <bits/stdc++.h> using namespace std; #define rint register int #define IL inline #define rep(i,h,t) for (rint i=h;i<=t;i++) #define dep(i,t,h) for (rint i=t;i>=h;i--) map<string,int> M1; map<int,string> M2; string s,s1,s2; int n,m; const int N=105; int f[N][N],dp[N][N],jl1[N],jl2[N]; struct re{ int a,b; }pre[N][N]; IL void maxa(int x,int y,int k,int a1,int b1) { if (dp[x][y]<k) { dp[x][y]=k; pre[x][y].a=a1; pre[x][y].b=b1; } } int main() { freopen("1.in","r",stdin); freopen("1.out","w",stdout); ios::sync_with_stdio(false); cin>>n>>m; rep(i,1,n) { cin>>s; M1[s]=i; M2[i]=s; } rep(i,1,m) { cin>>s1>>s2; int x1=M1[s1],x2=M1[s2]; if (x1>x2) swap(x1,x2); f[x1][x2]=1; } #define pd(x,y) ((x!=y)||(x==n)) dp[1][1]=1; rep(i,1,n) rep(j,1,n) if (dp[i][j]) { int tmp=dp[i][j]+1; rep(k,min(max(i,j)+1,n),n) { if (f[i][k]) maxa(k,j,tmp,i,j); if (f[j][k]) maxa(i,k,tmp,i,j); } } if (dp[n][n]) { cout<<dp[n][n]-1<<endl; int now1=n,now2=n,cnt1=0,cnt2=0; while (!(now1==1&&now2==1)) { if (pre[now1][now2].a!=now1) { jl1[++cnt1]=pre[now1][now2].a; now1=pre[now1][now2].a; } else { jl2[++cnt2]=pre[now1][now2].b; now2=pre[now1][now2].b; } } dep(i,cnt1,1) cout<<M2[jl1[i]]<<endl; cout<<M2[n]<<endl; rep(i,1,cnt2) cout<<M2[jl2[i]]<<endl; } else cout<<"No Solution!"<<endl; return 0; }
11.方格取数问题
裸题,可以发现网格图是个二分图,按照行号+列号的奇偶性来判断就行了
12.机器人路径规划问题(应该是题目有问题,如果起点是叶子节点就可以直接网络流了)
13.圆桌问题
14.骑士共存问题
15.火星探险问题
16.最长k可重线段集问题(ok 前面篇写过了)
17.最长k可重区间集问题(ok前面篇写过了)
18.汽车加油行驶问题
19.孤岛营救问题
20.深海机器人问题
21.数字梯形问题
22.分配问题
23.运输问题
24.负载平衡问题