蒟蒻的网络流24题解题记

来源:互联网 发布:淘宝访客只有二三十 编辑:程序博客网 时间:2024/06/12 21:23

食用手册

网络流24题除了暂时没有优秀解法的“机器人路径规划问题”以外,皆可在loj上食用。
关于网络流的部分问题,可参见本蒟蒻“网络流”专题的博客。
EK最大流算法
dinic最大流算法
最小费用最大流
以及安利xzy神犇的binic(即“比你快”算法)最大流算法:http://k-xzy.cf/archives/3008
祝食用愉快。

1.飞行员配对方案问题

二分图匹配问题,图方便还是用了匈牙利算法。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int cp[105],vis[105],h[105],to[100005],ne[100005];int n,m,ans,tot;void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}int dfs(int x) {    for(int i=h[x];i;i=ne[i])        if(!vis[to[i]]) {            vis[to[i]]=1;            if(!cp[to[i]]||dfs(cp[to[i]])) {cp[to[i]]=x;return 1;}        }    return 0;}int main(){    int x,y;    scanf("%d%d",&n,&m);    while(scanf("%d%d",&x,&y)==2) add(x,y);    for(int i=1;i<=n;++i) {        for(int j=1;j<=m;++j) vis[j]=0;        if(dfs(i)) ++ans;    }    printf("%d",ans);    return 0;}

2.太空飞行计划问题

最大权闭合子图问题。
如果您需要理性的证明,参见胡伯涛的论文或者:这里(然而这里其实也没有什么太严谨的证明,还是看论文吧)
首先源点向所有实验连一条(s,实验编号,实验可得费用)的边,所有仪器向汇点连一条(仪器编号,t,仪器花费)的边。然后每一个实验向所需的仪器连边。
我们继续把网络流比作水流的方法来看这个建图,每个实验上汇集了一些水,但是会随着所需的仪器”流走”,流走后可能还剩了一点水,这就是通过这个实验能赚的钱,综上,答案应该为“实验总可得费用-最大流”。
然后看方案。跑完最大流后的残图中,如果源点还可达某个点,这个点是实验,说明还有“残留的水”,该实验可取。如果是仪器,因为残图一定被“割开”了,源点能到的点到不了汇点,所以说明这个仪器被选用了,因此我们可以得到方案。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int inf=0x3f3f3f3f;const int N=(105+50*50)*2;int n,m,tot=1,s,t,ans;int h[105],to[N],ne[N],flow[N],q[105],lev[105],vis[105];void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int sum=0,kl;    for(int i=h[x];i;i=ne[i])        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {        kl=dfs(to[i],min(liu-sum,flow[i]));        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;        if(sum==liu) return sum;    }    return sum;}int bfs() {    int x,he=1,ta=1;    for(int i=s;i<=t;++i) lev[i]=0;    lev[s]=1,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&!lev[to[i]])                lev[to[i]]=lev[x]+1,q[++ta]=to[i];    }    return 0;}void gs(int x) {    for(int i=h[x];i;i=ne[i])        if(!vis[to[i]]&&flow[i]>0) vis[to[i]]=1,gs(to[i]);}int main(){    int x;    scanf("%d%d",&m,&n);    s=0,t=m+n+1;    for(int i=1;i<=m;++i) {        scanf("%d",&x),add(s,i,x),ans+=x;        char ch=getchar();        while(ch!='\n'&&ch!='\r'&&ch!=EOF) {            x=0;            while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();            if(x) add(i,m+x,inf);            if(ch!='\n'&&ch!='\r'&&ch!=EOF) ch=getchar();        }    }    for(int i=1;i<=n;++i) scanf("%d",&x),add(i+m,t,x);    while(bfs()) ans-=dfs(s,inf);    vis[s]=1,gs(s);    for(int i=1;i<=m;++i) if(vis[i]) printf("%d ",i);    putchar('\n');    for(int i=1;i<=n;++i) if(vis[i+m]) printf("%d ",i);    putchar('\n');    printf("%d",ans);    return 0;}

3.最小路径覆盖问题

首先每个点是一条单独的路径,每次可以选择一条边,使得两个路径被合并起来,这是一个二分图匹配问题。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=205,M=6005;int n,m,tot,ans;int h[N],ne[M],to[M],pre[N],vis[N],nxt[N];void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}int dfs(int x) {    for(int i=h[x];i;i=ne[i])        if(!vis[to[i]]) {            vis[to[i]]=1;            if(!pre[to[i]]||dfs(pre[to[i]]))             {pre[to[i]]=x,nxt[x]=to[i];return 1;}        }    return 0;}int main(){    int x,y;    scanf("%d%d",&n,&m);    for(int i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y);    ans=n;    for(int i=1;i<=n;++i) {        for(int j=1;j<=n;++j) vis[j]=0;        if(dfs(i)) --ans;    }    for(int i=1;i<=n;++i)        if(!pre[i]) {        x=i;        while(x) printf("%d ",x),x=nxt[x];        putchar('\n');    }    printf("%d",ans);    return 0;}

4.魔术球问题

其实可以贪心做(汗
参考最小路径覆盖问题,二分球的个数,对每个球建点,x向满足x+y是完全平方数且y>x的一个y连边,跑最小路径覆盖检验答案,如果所需路径小于等于n即满足条件。
如果用匈牙利算法的话,二分还要卡卡常(先打出最大的答案,然后嘿嘿嘿)
当然,其实如果使用网络流来搞二分图匹配的花,每次加一条边后继续跑网络流是很快的,因为原本就已经生成了残量网络了……枚举过毫无压力……

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;const int N=500005;int n,tot,ans,t;int h[N],ne[N],to[N],flow[N],vis[N],pre[N],nxt[N];void add(int x,int y){to[++tot]=y,ne[tot]=h[x],h[x]=tot;}int dfs(int x) {//辣鸡匈牙利算法    for(int i=h[x];i;i=ne[i])        if(!vis[to[i]]) {        vis[to[i]]=1;        if(!pre[to[i]]||dfs(pre[to[i]]))            {pre[to[i]]=x,nxt[x]=to[i];return 1;}    }    return 0;}int work(int lim) {//辣鸡建图+检验答案    int x1=lim*2-1,x2=lim*2,js=lim;    for(int i=1;i<=lim*2;++i) h[i]=pre[i]=nxt[i]=0;tot=0;    for(int i=1;i<=lim;++i) {        for(int j=sqrt(i);j*j<i*2;++j)        if(j*j>i) add((j*j-i)*2-1,i*2);    }    for(int i=1;i<=lim*2;i+=2) {        for(int j=2;j<=lim*2;j+=2) vis[j]=0;        if(dfs(i)) --js;    }    if(js>n) return 0;    return 1;}int main(){    scanf("%d",&n);    int l=n,r=1567,mid;    while(l<=r) {//二分答案        mid=(l+r)>>1;        if(work(mid)) ans=mid,l=mid+1;        else r=mid-1;    }    printf("%d\n",ans);work(ans);    for(int i=1;i<=ans;++i)        if(!pre[i*2]) {        int x=i*2-1;        while(x+1!=0) printf("%d ",x/2+1),x=nxt[x]-1;        putchar('\n');    }    return 0;}

5.圆桌问题

其实可以贪心做(汗
最小割问题。首先把每一个代表团建一个点,s向它们各连一条流量为代表团人数的边。然后每一个代表团向每一张餐桌连一条流量为1的边,然后每个餐桌向汇点连一条流量为餐桌可容纳人数的边。最后看哪些边被割掉了即可求解。
由于建图比较直观就不作更多说明了。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=450,M=100005,inf=0x3f3f3f3f;int h[N],to[M],ne[M],flow[M],lev[N],q[N];int tot=1,n,m,s,t,sum;void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int kl,sum=0;    for(int i=h[x];i;i=ne[i])        if(lev[to[i]]==lev[x]+1&&flow[i]>0) {        kl=dfs(to[i],min(flow[i],liu-sum));        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;        if(sum==liu) return sum;    }    return sum;}int bfs() {    int he=1,ta=1,x;    for(int i=s;i<=t;++i) lev[i]=0;    lev[s]=1,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(!lev[to[i]]&&flow[i]>0)            lev[to[i]]=lev[x]+1,q[++ta]=to[i];    }    return 0;}int main(){    int x,js=0;    scanf("%d%d",&m,&n);    s=0,t=m+n+1;    for(int i=1;i<=m;++i) scanf("%d",&x),sum+=x,add(s,i,x);    for(int i=1;i<=n;++i) scanf("%d",&x),add(i+m,t,x);    for(int i=1;i<=m;++i)        for(int j=1;j<=n;++j) add(i,j+m,1);    while(bfs()) js+=dfs(s,inf);    if(js!=sum) puts("0");    else {        puts("1");        for(int i=1;i<=m;++i) {            for(int j=h[i];j;j=ne[j])                if(flow[j]==0&&to[j]!=s) printf("%d ",to[j]-m);            putchar('\n');        }    }    return 0;}

6.最长递增子序列问题

这题题面简直有点不知所云。第二问是同时选出长度为s的递增序列(不可重叠),最多可以选多少个。第三问中不可以连续使用多个x1xn
这题的建图很巧妙,给跪了。
首先用O(n2)的dp瞎搞一遍,获得从每一个位置i开始可以构成的最长子序列长度fi
将每一个点拆成i1i2。令length表示最长递增子序列长度,那么对于每一个点,如果fi=length,连边(s,i1,1),因为它可以作为一个序列的起点。如果fi=1,连边(i2,t,1),因为它可以作为一个序列的终点。此外,连边(i1,i2,1),这是保证每一个点只被用一次。最后,如果存在j>ifj+1=fiajai,那么连边(i2,j1,1),这样可以模拟选择序列的过程。
求一遍最大流得到第二问的解。
如果存在(s,11,1),添加(s,11,inf),然后添加(11,12,inf)(n1,n2,inf)(n2,t,inf),借着上一问搞完的残量图继续求最大流即可得到第三问的解,这几次修改就是由于x1xn的选择次数不受控制后引起的。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=505,M=500*500*2+5,inf=0x3f3f3f3f;int n,s,t,len,tot=1,js;int a[N],h[N<<1],ne[M],to[M],flow[M],lev[N<<1],q[N<<1],f[N];void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int sum=0,kl;    for(int i=h[x];i;i=ne[i])        if(lev[to[i]]==lev[x]+1&&flow[i]>0) {        kl=dfs(to[i],min(liu-sum,flow[i]));        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;        if(sum==liu) return sum;    }    return sum;}int bfs() {    for(int i=s;i<=t;++i) lev[i]=0;    int he=1,ta=1,x;    lev[s]=1,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(!lev[to[i]]&&flow[i]>0)            lev[to[i]]=lev[x]+1,q[++ta]=to[i];    }    return 0;}int main(){    scanf("%d",&n);s=0,t=n*2+1;    for(int i=1;i<=n;++i) scanf("%d",&a[i]);    for(int i=n;i>=1;--i) {        f[i]=1;        for(int j=i+1;j<=n;++j)            if(a[j]>=a[i]&&f[j]+1>f[i]) f[i]=f[j]+1;        len=max(len,f[i]);    }    printf("%d\n",len);    for(int i=1;i<=n;++i) {        if(f[i]==len) add(s,i,1);        if(f[i]==1) add(i+n,t,1);        add(i,i+n,1);        for(int j=i+1;j<=n;++j)            if(a[j]>=a[i]&&f[i]==f[j]+1) add(i+n,j,1);    }    while(bfs()) js+=dfs(s,inf);    printf("%d\n",js);    if(f[1]==len) add(s,1,inf);    add(1,n+1,inf),add(n,n+n,inf),add(n+n,t,inf);    while(bfs()) js+=dfs(s,inf);    printf("%d\n",js);    return 0;}

7.试题库问题

这……这不是个easy的类二分图匹配问题吗╮(╯_╰)╭
那么只要源点往每个题上连一条流量为1的边,题往其特征上连一条流量为1的边,特征往汇点上连一条流量为该特征需求量的边,然后跑最大流检验答案并输出方案。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=1025,M=20*1000*2+1025,inf=0x3f3f3f3f;int s,t,n,m,tot=1,sum,js;int h[N],ne[M],to[M],flow[M],q[N],lev[N];void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int sum=0,kl;    for(int i=h[x];i;i=ne[i])        if(lev[to[i]]==lev[x]+1&&flow[i]>0) {        kl=dfs(to[i],min(liu-sum,flow[i]));        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;        if(sum==liu) return sum;    }    return sum;}int bfs() {    for(int i=s;i<=t;++i) lev[i]=0;    int he=1,ta=1,x;    lev[s]=1,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(!lev[to[i]]&&flow[i]>0)            lev[to[i]]=lev[x]+1,q[++ta]=to[i];    }    return 0;}int main(){    int x,num;    scanf("%d%d",&m,&n);s=0,t=n+m+1;    for(int i=1;i<=m;++i) scanf("%d",&x),add(i+n,t,x),sum+=x;    for(int i=1;i<=n;++i) {        scanf("%d",&num);add(s,i,1);        for(int j=1;j<=num;++j) scanf("%d",&x),add(i,x+n,1);    }    while(bfs()) js+=dfs(s,inf);    if(js!=sum) {puts("No Solution!");return 0;}    for(int i=1;i<=m;++i) {        printf("%d:",i);        for(int j=h[i+n];j;j=ne[j])            if(flow[j^1]==0&&to[j]!=t) printf(" %d",to[j]);        putchar('\n');    }    return 0;}

8.机器人路径规划问题

–此问题未解决,溜了溜了,如果本蒟蒻哪天脑子抽了可能会用搜索之类的方法试一下这题–

9.方格取数问题

最大点权独立集问题。
我们可以将原图交错染成天依蓝和nico粉两色,则分成了蓝色组和粉色组。
很不幸,相邻的蓝色点和粉色点是无法共存的。
我们先求解第一个问题:一条边相连的两点至少选一个,得到的最小权值和。
我们首先选择所有的蓝色点,即将源点和所有的蓝色点连一条流量为蓝色点点权的边。而将所有粉色点和汇点连一条流量为粉色点点权的边。
现在我们要改变我们既定的选择。由于当前流量已经确定了,所以我们用图内流量去“填”粉色点代表的边的时候,不可能发生权值增多,只有可能减少或者不变。这样感性地理解一下,会发现最后得到的最大流就是问题一的解。
而我们现在的问题是一条边相连的两点只能选一个,求最大权值和。
则用权值总和-最大流即可。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=30*30+5,M=30*30*10+5,inf=0x3f3f3f3f;int mvx[6]={0,1,-1,0,0},mvy[6]={0,0,0,1,-1};int h[N],ne[M],to[M],flow[M],lev[N],q[N];int n,m,tot=1,s,t,sss,ans;void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int kl,sum=0;    for(int i=h[x];i;i=ne[i])        if(lev[to[i]]==lev[x]+1&&flow[i]>0) {        kl=dfs(to[i],min(liu-sum,flow[i]));        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;        if(sum==liu) return sum;    }    return sum;}int bfs() {    for(int i=s;i<=t;++i) lev[i]=0;    int x,he=1,ta=1;lev[s]=1,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(!lev[to[i]]&&flow[i]>0)            q[++ta]=to[i],lev[to[i]]=lev[x]+1;    }    return 0;}int main(){    int x;    scanf("%d%d",&n,&m);    s=0,t=n*m+1;    for(int i=1;i<=n;++i)        for(int j=1;j<=m;++j) {        scanf("%d",&x),sss+=x;        if((i+j)%2==0) {add((i-1)*m+j,t,x);continue;}        add(s,(i-1)*m+j,x);        for(int k=1;k<=4;++k) {            int tx=i+mvx[k],ty=j+mvy[k];            if(tx>=1&&tx<=n&&ty>=1&&ty<=m)                add((i-1)*m+j,(tx-1)*m+ty,inf);        }    }    while(bfs()) ans+=dfs(s,inf);    printf("%d",sss-ans);    return 0;}

10.餐巾计划问题

最小费用最大流QWQ
还是很明显的,首先要把一天拆成两个点,一个表示当天过后脏餐巾i1,一个表示当天开始前的干净餐巾i2
由于干净餐巾需要ri个,所以连边(i2,t,ri,0)用于检验当天餐巾数是否符合要求。
由于脏餐巾每天会产生ri个,所以连边(s,i1,ri,0),这个是我在自己做该题时没有想出来的,我以为要从干净餐巾处把脏餐巾转移过来导致一直没有做出来QAQ
每天可以买餐巾(s,i2,inf,p)
可以把脏餐巾不洗留到明天(i1,(i+1)1,inf,0)
可以送快洗部或者慢洗部(i1,(i+m)2,inf,f)(i1,(i+n)2,inf,s)
然后最小费用最大流就是答案啦。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>using namespace std;const int N=2005,inf=0x3f3f3f3f;int n,p,m1,m2,f1,f2,s,t,tot=1,ans,mx;int h[N],to[N*6],ne[N*6],flow[N*6],w[N*6],dis[N],pre[N],inq[N],liu[N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=inq[i]=0;    queue<int> q;int x;    dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {                dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;                liu[to[i]]=min(liu[x],flow[i]);                if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);            }    }    if(!pre[t]) return 0;    mx+=liu[t],x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=liu[t]*w[kl],x=to[kl^1];    }    return 1;}int main(){    int x;    scanf("%d%d%d%d%d%d",&n,&p,&m1,&f1,&m2,&f2);    s=0,t=n*2+1;    for(int i=1;i<=n;++i) {        scanf("%d",&x);        add(s,i,x,0),add(i+n,t,x,0),add(s,i+n,inf,p);        if(i+1<=n) add(i,i+1,inf,0);        if(i+m1<=n) add(i,n+i+m1,inf,f1);        if(i+m2<=n) add(i,n+i+m2,inf,f2);    }    while(bfs());    printf("%d",ans);    return 0;}

11.航空路线问题

题目又没有说清楚,其实路上每一步都只能从东边的城市到西边的。不然跑网络流是会出现死循环的。
所以拆点,连边(11,12,2,1)(n1,n2,2,1),其他的每个i1i2连边(i1,i2,1,1),对于每条图上有的边(x,y),(x小于y)连边(x2,y1,inf,0)。跑完最小费用最大流后,要把答案取相反数(因为连边用的费用是负数嘛)
然后输出方案看代码吧,其实就是看哪些边上有流量,求两次即可。

#include<bits/stdc++.h>using namespace std;map<string,int> mp;const int N=205,M=205*205*2,inf=0x3f3f3f3f;string name[105];int n,m,tot=1,s,t,mx,ans;int h[N],to[M],flow[M],ne[M],w[M],dis[N],pre[N],liu[N],inq[N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=liu[i]=0;    queue<int> q;int x;    dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(liu[x],flow[i]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t,mx+=liu[t];    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=w[kl]*liu[t],x=to[kl^1];    }    return 1;}void find1(int x) {    cout<<name[x]<<endl;    if(x==n) return;    for(int i=h[x+n];i;i=ne[i])        if(w[i]==0&&flow[i^1]!=0&&to[i]<=n)         {flow[i^1]=0,find1(to[i]);return;}}void find2(int x) {    if(x==n) return;    for(int i=h[x+n];i;i=ne[i])        if(w[i]==0&&flow[i^1]!=0&&to[i]<=n)        {find2(to[i]);break;}    cout<<name[x]<<endl;}int main(){    string x,y;    scanf("%d%d\n",&n,&m);s=0,t=n*2+1;    for(int i=1;i<=n;++i) cin>>x,mp[x]=i,name[i]=x;    for(int i=1;i<=m;++i) {        cin>>x>>y;        if(mp[x]>mp[y]) swap(x,y);        add(mp[x]+n,mp[y],inf,0);    }    add(s,1,2,0),add(n+n,t,2,0),add(1,n+1,2,-1),add(n,n+n,2,-1);    for(int i=2;i<n;++i) add(i,i+n,1,-1);    while(bfs());    if(mx!=2) {puts("No Solution!");return 0;}    ans=-ans-2;printf("%d\n",ans);    find1(1),find2(1);    return 0;}

12.软件补丁问题

震惊!网络流24题中竟有如此多不是网络流的题目,这到底是道德的沦丧还是人性的扭曲?
好吧,这题其实是一个可爱的最短路问题,一个bug状态就是一个点。由于边数比较多,所以不预先建边,而是在跑spfa的过程中建边。熟悉位运算的人应该不难写出转移方法。

#include<bits/stdc++.h>using namespace std;const int N=2097152,inf=0x3f3f3f3f;int n,m,tot;int bin[25],dis[N],inq[N];int w[105],b1[105],b2[105],f1[105],f2[105];void spfa() {    int x,y;queue<int> q;    for(int i=0;i<bin[n+1];++i) dis[i]=inf;    dis[bin[n+1]-1]=0,q.push(bin[n+1]-1);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=1;i<=m;++i) {            if((x&b2[i])||((x&b1[i])!=b1[i])) continue;            y=x^(x&f1[i]),y|=f2[i];            if(dis[x]+w[i]<dis[y]) {                dis[y]=dis[x]+w[i];                if(!inq[y]) inq[y]=1,q.push(y);            }        }    }    if(dis[0]<inf) printf("%d\n",dis[0]);    else puts("0");}int main(){    char s[33];    scanf("%d%d",&n,&m);    bin[1]=1;for(int i=2;i<=n+1;++i) bin[i]=bin[i-1]<<1;    for(int i=1;i<=m;++i) {        scanf("%d",&w[i]);        scanf("%s",s);        for(int j=1;j<=n;++j)             if(s[j-1]=='-') b2[i]|=bin[j];            else if(s[j-1]=='+') b1[i]|=bin[j];        scanf("%s",s);        for(int j=1;j<=n;++j)            if(s[j-1]=='+') f2[i]|=bin[j];            else if(s[j-1]=='-') f1[i]|=bin[j];    }    spfa();    return 0;}

13.星际转移问题

思路:枚举+并查集+最大流
用并查集判断是否有解法。将一艘飞船可以到达的所有星球并查集连起来,最后如果地球和月球无法连接,则无解。
然后枚举答案。
所有的点为“第i个星际站在第t秒”这样一个状态的点,那么枚举的答案每增加1,就需要新建“一套”地球和太空站的点。
源点向每一个“地球”连一条容量为inf的边,每个空间站向下一时间的该空间站连一条容量为inf的边,代表时间间的转移。
每个飞船现在在哪个星球,下一秒会飞到哪一个星球都可以计算得到,所以直接连边,容量为飞船载人量。
月球就是汇点。
然后跑最大流,如果最大流大于需要转移的人数了,那么就得到了解。枚举然后每次建立新边,在残量网络上跑最大流,反而比二分答案后建立新图重跑最大流快。

#include<bits/stdc++.h>using namespace std;const int inf=0x3f3f3f3f,M=1000005;int n,m,k,s,t,tot=1,ans,mx;int f[100],p[100],g[100][100],num[100];int ne[M],to[M],h[M],flow[M],lev[M],q[M];int find(int x) {    if(f[x]==x) return x;    f[x]=find(f[x]);return f[x];}void uni(int x,int y) {    x=find(x),y=find(y);    if(x!=y) f[x]=y;}void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int kl,sum=0;    for(int i=h[x];i;i=ne[i])        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {        kl=dfs(to[i],min(flow[i],liu-sum));        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;        if(sum==liu) return sum;    }    return sum;}int bfs() {    for(int i=1;i<=ans*(n+1);++i) lev[i]=0;    int he=1,ta=1,x;lev[t]=0,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&!lev[to[i]])            lev[to[i]]=lev[x]+1,q[++ta]=to[i];    }    return 0;}int main(){    int x,y;    scanf("%d%d%d",&n,&m,&k);    s=0,t=M-2;    for(int i=1;i<=n+2;++i) f[i]=i;    for(int i=1;i<=m;++i) {        scanf("%d%d",&p[i],&num[i]);        for(int j=0;j<num[i];++j) {            scanf("%d",&g[i][j]);            if(g[i][j]==0) g[i][j]=n+1;            if(g[i][j]==-1) g[i][j]=n+2;            if(j!=0) uni(g[i][j-1],g[i][j]);        }    }    if(find(n+1)!=find(n+2)) {puts("0");return 0;}    for(ans=1;;++ans) {        add(s,(ans-1)*(n+1)+n+1,inf);        for(int i=1;i<=m;++i) {            x=(ans-1)%num[i],y=ans%num[i];            if(g[i][x]==n+2) x=t;            else x=(ans-1)*(n+1)+g[i][x];            if(g[i][y]==n+2) y=t;            else y=ans*(n+1)+g[i][y];            add(x,y,p[i]);        }        while(bfs()) mx+=dfs(s,inf);        if(mx>=k) {printf("%d\n",ans);return 0;}        for(int i=1;i<=n+1;++i) add((ans-1)*(n+1)+i,ans*(n+1)+i,inf);    }    return 0;}

14.孤岛营救问题

这和12题一样是一个最短路问题,同样也是状态建图。
用(所在位置,所持钥匙集合)作为一个点,然后判断一下点之间的转移进行最短路即可。

#include<bits/stdc++.h>using namespace std;const int inf=0x3f3f3f3f;int n,m,p,T,tot,ans=inf;int bin[14];int l[105][105],key[105],dis[105][2048],inq[105][2048];int mvx[7]={0,0,0,1,-1},mvy[7]={0,1,-1,0,0};struct node{int x,y,have;};void spfa() {    for(int i=1;i<=n*m;++i)        for(int j=0;j<bin[p+1];++j) dis[i][j]=inf;    queue<node>q; node u,v;int xx,tx,ty,yy;    dis[1][key[1]]=0,q.push((node){1,1,key[1]});    while(!q.empty()) {        u=q.front(),xx=(u.x-1)*m+u.y;        q.pop(),inq[xx][u.have]=0;        for(int i=1;i<=4;++i) {            tx=u.x+mvx[i],ty=u.y+mvy[i],yy=(tx-1)*m+ty;            if(tx<1||tx>n||ty<1||ty>m) continue;            if(l[xx][yy]==0) continue;            if(l[xx][yy]>0&&!(u.have&bin[l[xx][yy]])) continue;            v.x=tx,v.y=ty,v.have=u.have|key[yy];            if(dis[xx][u.have]+1<dis[yy][v.have]) {                dis[yy][v.have]=dis[xx][u.have]+1;                if(!inq[yy][v.have]) inq[yy][v.have]=1,q.push(v);            }        }    }    for(int i=0;i<bin[p+1];++i) ans=min(ans,dis[n*m][i]);    if(ans<inf) printf("%d\n",ans);    else puts("-1");}int main(){    int x1,y1,x2,y2,g;    scanf("%d%d%d",&n,&m,&p);    bin[1]=1;for(int i=2;i<=p+1;++i) bin[i]=bin[i-1]<<1;    memset(l,-1,sizeof(l));    scanf("%d",&T);    while(T--) {        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);        l[(x1-1)*m+y1][(x2-1)*m+y2]=l[(x2-1)*m+y2][(x1-1)*m+y1]=g;    }    scanf("%d",&T);    while(T--) scanf("%d%d%d",&x1,&y1,&g),key[(x1-1)*m+y1]|=bin[g];    spfa();    return 0;}

15.汽车加油行驶问题

还是状态最短路问题,建图方式很巧妙以至于我这个sb一开始没有想到……
显然,状态作为点的情况下,应该是位置+剩余油量这样一个状态,不过由于细节很多容易出错:
1.对于有加油站的点,因为强制加满油,所以只有满油状态可以向周围连边
2.对于没有加油站的点,只有没油了的时候才要建立加油站,所以可以减少一些边(防止RE)
3.没油状态不可以进行位置之间的转移

#include<bits/stdc++.h>using namespace std;const int N=101*101*11+5,inf=0x3f3f3f3f;int h[N],ne[N*5],to[N*5],w[N*5],dis[N],inq[N];int mvx[7]={0,0,0,1,-1},mvy[7]={0,1,-1,0,0};int n,kk,a,b,c,tot,ans=inf;void add(int x,int y,int z){to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}void spfa() {    memset(dis,0x3f,sizeof(dis));    int x;queue<int>q;    dis[1*(kk+1)+kk]=0,q.push(1*(kk+1)+kk);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i];            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    for(int oil=0;oil<=kk;++oil) ans=min(ans,dis[n*n*(kk+1)+oil]);    printf("%d\n",ans);}int main(){    int xx,yy,tmp;    scanf("%d%d%d%d%d",&n,&kk,&a,&b,&c);    for(int i=1;i<=n;++i)        for(int j=1;j<=n;++j) {        scanf("%d",&tmp);xx=(i-1)*n+j;        for(int k=1;k<=4;++k) {            int tx=i+mvx[k],ty=j+mvy[k];yy=(tx-1)*n+ty;            if(tx<1||tx>n||ty<1||ty>n) continue;            if(!tmp)                for(int oil=1;oil<kk;++oil)//转移位置                add(xx*(kk+1)+oil,yy*(kk+1)+oil-1,(mvx[k]+mvy[k]==-1)*b);            add(xx*(kk+1)+kk,yy*(kk+1)+kk-1,(mvx[k]+mvy[k]==-1)*b);        }        if(!tmp) add(xx*(kk+1),xx*(kk+1)+kk,a+c);//建立加油站        else for(int oil=0;oil<kk;++oil)//加油                add(xx*(kk+1)+oil,xx*(kk+1)+kk,a);    }    spfa();    return 0;}

16.数字梯形问题

费用流解决路径交问题。然而这个题面又让人很迷惑,其实是(i,j)是一条路,问3中(i,j)这条路可以被多次使用而已。
那么就用拆点+费用流就可以轻松解决了。
第一问建立(s,第一行的点1,1,0),(最后一行的点2,t,1,0),(i2,j1,1,0),(i1,i2,1,wi)
第二问建立(s,第一行的点1,1,0),(最后一行的点2,t,inf,0),(i2,j1,1,0),(i1,i2,inf,wi)
第二问建立(s,第一行的点1,1,0),(最后一行的点2,t,inf,0),(i2,j1,inf,0),(i1,i2,inf,wi)
注意以下两点(都是本蒟蒻犯的错误):
1.费用流不可以在加边后直接重跑残量网络,这样得不到答案,必须把整张图推翻重建。
2.本蒟蒻一开始把所有建图中为inf的地方都写的是2…..

#include<bits/stdc++.h>using namespace std;const int N=805,M=1000*12+5,inf=0x3f3f3f3f;int n,m,tot=1,ans,mx,s,t,now;int h[N],ne[M],to[M],flow[M],w[M];int dis[N],pre[N],inq[N],ma[44][44],num[N],liu[N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    queue<int> q;int x;    for(int i=s;i<=t;++i) pre[i]=inq[i]=0,dis[i]=inf;    dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(flow[i],liu[x]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=w[kl]*liu[t],x=to[kl^1];    }    return 1;}void work1() {    for(int i=1;i<=now;++i) add(i,now+i,1,-num[i]);    for(int i=1;i<=m;++i) add(s,i,1,0);    for(int i=1;i<=m+n-1;++i) add(ma[n][i]+now,t,1,0);    for(int i=1;i<n;++i)        for(int j=1;j<=m+i-1;++j)        add(ma[i][j]+now,ma[i+1][j],1,0),add(ma[i][j]+now,ma[i+1][j+1],1,0);    while(bfs()); printf("%d\n",-ans);}void work2() {    memset(h,0,sizeof(h));tot=1,ans=0;    for(int i=1;i<=now;++i) add(i,now+i,inf,-num[i]);    for(int i=1;i<=m;++i) add(s,i,1,0);    for(int i=1;i<=m+n-1;++i) add(ma[n][i]+now,t,inf,0);    for(int i=1;i<n;++i)        for(int j=1;j<=m+i-1;++j)        add(ma[i][j]+now,ma[i+1][j],1,0),add(ma[i][j]+now,ma[i+1][j+1],1,0);    while(bfs()); printf("%d\n",-ans);}void work3() {    memset(h,0,sizeof(h));tot=1,ans=0;    for(int i=1;i<=now;++i) add(i,now+i,inf,-num[i]);    for(int i=1;i<=m;++i) add(s,i,1,0);    for(int i=1;i<=m+n-1;++i) add(ma[n][i]+now,t,inf,0);    for(int i=1;i<n;++i)        for(int j=1;j<=m+i-1;++j)        add(ma[i][j]+now,ma[i+1][j],inf,0),        add(ma[i][j]+now,ma[i+1][j+1],inf,0);    while(bfs()); printf("%d\n",-ans);}int main(){    scanf("%d%d",&m,&n);    for(int i=1;i<=n;++i)        for(int j=1;j<=m+i-1;++j)            ma[i][j]=++now,scanf("%d",&num[now]);    s=0,t=now*2+1;work1(),work2(),work3();    return 0;}

17.运输问题

可以说是很裸的费用流问题了。
源点向每个仓库连一条流量为仓库储存量的边,每个商店向汇点连一条流量为需求量的边,仓库向商店连流量为inf,费用为运输费用的边。分别跑最大费用流和最小费用流即可。

#include<bits/stdc++.h>using namespace std;const int N=205,M=200*200*2+205,inf=0x3f3f3f3f;int n,m,tot=1,s,t,ans;int h[N],to[M],ne[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];int a[N],b[N],c[N][N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    queue<int> q;int x;    for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=0;    dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(liu[x],flow[i]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=liu[t]*w[kl],x=to[kl^1];    }    return 1;}void work1() {    for(int i=1;i<=m;++i) scanf("%d",&a[i]),add(s,i,a[i],0);    for(int i=1;i<=n;++i) scanf("%d",&b[i]),add(i+m,t,b[i],0);    for(int i=1;i<=m;++i)        for(int j=1;j<=n;++j)        scanf("%d",&c[i][j]),add(i,j+m,inf,c[i][j]);    while(bfs());printf("%d\n",ans);}void work2() {    memset(h,0,sizeof(h));tot=1,ans=0;    for(int i=1;i<=m;++i) add(s,i,a[i],0);    for(int i=1;i<=n;++i) add(i+m,t,b[i],0);    for(int i=1;i<=m;++i)        for(int j=1;j<=n;++j)        add(i,j+m,inf,-c[i][j]);    while(bfs());printf("%d\n",-ans);}int main(){    scanf("%d%d",&m,&n);    s=0,t=m+n+1;work1(),work2();    return 0;}

18.分配问题

这…..这不是和上一题一毛一样的吗,直接改一改上一题代码即可啊。

#include<bits/stdc++.h>using namespace std;const int N=205,M=200*200*2+205,inf=0x3f3f3f3f;int n,m,tot=1,s,t,ans;int h[N],to[M],ne[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];int c[N][N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    queue<int> q;int x;    for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=0;    dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(liu[x],flow[i]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=liu[t]*w[kl],x=to[kl^1];    }    return 1;}void work1() {    for(int i=1;i<=m;++i) add(s,i,1,0);    for(int i=1;i<=n;++i) add(i+m,t,1,0);    for(int i=1;i<=m;++i)        for(int j=1;j<=n;++j)        scanf("%d",&c[i][j]),add(i,j+m,inf,c[i][j]);    while(bfs());printf("%d\n",ans);}void work2() {    memset(h,0,sizeof(h));tot=1,ans=0;    for(int i=1;i<=m;++i) add(s,i,1,0);    for(int i=1;i<=n;++i) add(i+m,t,1,0);    for(int i=1;i<=m;++i)        for(int j=1;j<=n;++j) add(i,j+m,inf,-c[i][j]);    while(bfs());printf("%d\n",-ans);}int main(){    scanf("%d",&n);m=n;    s=0,t=m+n+1;work1(),work2();    return 0;}

19.负载平衡问题

非常明显的费用流建图方法,对于多于平均值x的物品,建一条从源点到这个点的边(s,i,aix,0),否则建边(i,t,xai,0)。然后互相建边(i,i+1,inf,1)和(i-1,i,inf,1)

#include<bits/stdc++.h>using namespace std;const int inf=0x3f3f3f3f,N=105,M=2005;int n,s,t,sum,ans,tot=1;int h[N],to[M],ne[M],flow[M],w[M],inq[N],liu[N],pre[N],dis[N],a[N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    queue<int>q;int x;    for(int i=s;i<=t;++i) pre[i]=inq[i]=0,dis[i]=inf;    liu[s]=inf,q.push(s),dis[s]=0;    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {                dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;                liu[to[i]]=min(liu[x],flow[i]);                if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);            }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=w[kl]*liu[t],x=to[kl^1];    }    return 1;}int main(){    scanf("%d",&n);s=0,t=n+1;    for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum+=a[i];    sum/=n;    for(int i=1;i<=n;++i) {        if(a[i]>sum) add(s,i,a[i]-sum,0);        else add(i,t,sum-a[i],0);        if(i!=1) add(i,i-1,inf,1);        else add(i,n,inf,1);        if(i!=n) add(i,i+1,inf,1);        else add(i,1,inf,1);    }    while(bfs());    printf("%d",ans);    return 0;}

20.深海机器人问题

费用流的建图还是比较明显的,因为只有一个机器人能捡起标本,所以两个点之间应该连两条路径,一条为(x,y,1,-c)一条为(x,y,inf,0)

#include<bits/stdc++.h>using namespace std;const int N=20*20+5,M=20*20*6+5,inf=0x3f3f3f3f;int a,b,n,m,tot=1,ans,s,t;int h[N],to[M],ne[M],flow[M],w[M],dis[N],liu[N],pre[N],inq[N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=inq[i]=0;    queue<int> q;int x;    dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {                dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;                liu[to[i]]=min(liu[x],flow[i]);                if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);            }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=liu[t]*w[kl],x=to[kl^1];    }    return 1;}int main(){    int x,y,k;    scanf("%d%d%d%d",&a,&b,&n,&m);    s=0,t=(n+1)*(m+1)+1;    for(int i=1;i<=n+1;++i)        for(int j=1;j<=m;++j) {        scanf("%d",&x);        add((m+1)*(i-1)+j,(m+1)*(i-1)+j+1,1,-x);        add((m+1)*(i-1)+j,(m+1)*(i-1)+j+1,inf,0);    }    for(int i=1;i<=m+1;++i)        for(int j=1;j<=n;++j) {        scanf("%d",&x);        add((m+1)*(j-1)+i,(m+1)*j+i,1,-x);        add((m+1)*(j-1)+i,(m+1)*j+i,inf,0);    }    for(int i=1;i<=a;++i)        scanf("%d%d%d",&k,&x,&y),add(s,x*(m+1)+y+1,k,0);    for(int i=1;i<=b;++i)        scanf("%d%d%d",&k,&x,&y),add(x*(m+1)+y+1,t,k,0);    while(bfs());    printf("%d",-ans);    return 0;}

21.最长k可重区间集问题

一个巧妙的费用流问题(本蒟蒻真做不出QAQ)。
我们需要解决的问题:
1.控制每个点被选的次数:可以想到这个可以通过流量来控制。
2.计算区间长度对答案带来的价值:可以想到用费用来搞。
所以得到了一个建图方法:离散端点(记得要去重!)后,将每个端点作为一个点,连边(i,i+1,inf,0)。设tt为最后一个端点,那么连边(s,1,k,0)(tt,t,k,0)。最后,如果有一个区间长度为l,连边(l,r,1,l)
这样,如果一个点作为坐端点,引一个单位的流量走了另一条路,那么大于左端点小于右端点的点们,能引出的区间个数就会减少1个(画个图就可以理解啦),保证了选点次数的限制不被打破。

#include<bits/stdc++.h>using namespace std;const int N=1005,M=5005,inf=0x3f3f3f3f;int n,m,s,t,ans,tot=1,tt;int h[N],ne[M],to[M],w[M],flow[M];int dis[N],liu[N],pre[N],inq[N],b[N],l[N],r[N];void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    for(int i=s;i<=t;++i) dis[i]=inf,inq[i]=pre[i]=0;    queue<int>q;int x; dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(liu[x],flow[i]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=liu[t]*w[kl],x=to[kl^1];    }    return 1;}int main(){    int x,y;    scanf("%d%d",&n,&m);    s=0,t=2*n+1;    for(int i=1;i<=n;++i) {        scanf("%d%d",&l[i],&r[i]);        if(l[i]>r[i]) swap(l[i],r[i]);        b[i]=l[i],b[i+n]=r[i];    }    sort(b+1,b+1+2*n);    tt=1;for(int i=2;i<=2*n;++i) if(b[i]!=b[tt]) b[++tt]=b[i];    add(s,1,m,0),add(tt,t,m,0);    for(int i=1;i<tt;++i) add(i,i+1,inf,0);    for(int i=1;i<=n;++i) {        x=lower_bound(b+1,b+1+tt,l[i])-b;        y=lower_bound(b+1,b+1+tt,r[i])-b;        add(x,y,1,l[i]-r[i]);    }    while(bfs());    printf("%d",-ans);    return 0;}

22.最长k可重线段集问题

网上所有题解都说和上一题差不多。。。
于是我就按照上一题的写法写了一下,TLE。。。
然后我看了loj上的AC代码,发现我忘记判断与x轴垂直的直线了,可能会连出自环导致跑费用流时死循环(注释部分)。改了之后顺利在loj上AC。。。
然后兴奋地跑到别的oj上去交,都是WA。。。
现在整个人都迷茫了。。。

#include<bits/stdc++.h>using namespace std;#define LL long longconst int N=1005,M=200005;const LL inf=1e14;int n,m,s,t,tot=1,tt;LL ans;int h[N],ne[M],to[M];LL w[M],flow[M];LL dis[N],liu[N],b[N];LL pre[N],inq[N];struct node{LL x,y;} p1[N],p2[N];void add(int x,int y,LL z,LL c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    for(int i=s;i<=t;++i) dis[i]=inf,inq[i]=pre[i]=0;    queue<int>q;int x; dis[s]=0,liu[s]=inf,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(liu[x],flow[i]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t];        ans+=liu[t]*w[kl],x=to[kl^1];    }    return 1;}int main(){    int x,y,v;    scanf("%d%d",&n,&m);    s=0,t=2*n+1;    for(int i=1;i<=n;++i) {        scanf("%lld%lld%lld%lld",&p1[i].x,&p1[i].y,&p2[i].x,&p2[i].y);        if(p1[i].x>p2[i].x) swap(p1[i],p2[i]);        b[i]=p1[i].x,b[i+n]=p2[i].x;    }    sort(b+1,b+1+2*n);    tt=1;for(int i=2;i<=2*n;++i) if(b[i]!=b[tt]) b[++tt]=b[i];    add(s,1,m,0),add(tt,t,m,0);    for(int i=1;i<tt;++i) add(i,i+1,inf,0);    for(int i=1;i<=n;++i) {        x=lower_bound(b+1,b+1+tt,p1[i].x)-b;        y=lower_bound(b+1,b+1+tt,p2[i].x)-b;        v=(LL)sqrt((p1[i].x-p2[i].x)*(p1[i].x-p2[i].x)+(p1[i].y-p2[i].y)*(p1[i].y-p2[i].y));        if(x==y) ++y;//注意这里,避免死循环        add(x,y,1,-v);    }    while(bfs());    printf("%lld",-ans);    return 0;}

23.火星探险问题

类似于第20题的一个费用流问题。由于20题标本在边上,所以在边上控制费用。而此题标本在点上,所以采用拆点的思想,如果一个点上有标本,连边(i1,i2,1,1)(i1,i2,inf,0),否则只用连(i1,i2,inf,0)。相连的节点i和j之间再连边(i2,j1,inf,0)即可。
至于输出方案,就是去检索残量网络的一个过程,可以通过检索反向边是否有流量来得到这条边是否被一辆车走过了。

#include<bits/stdc++.h>using namespace std;const int N=35*35*2+5,M=35*35*10+5,inf=0x3f3f3f3f;int h[N],to[M],ne[M],flow[M],w[M],liu[N],dis[N],pre[N],inq[N];int ma[40][40];int car,n,m,s,t,tot=1;void add(int x,int y,int z,int c) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;}int bfs() {    queue<int>q;int x;    for(int i=s;i<=t;++i) dis[i]=inf,pre[i]=inq[i]=0;    liu[s]=inf,dis[s]=0,q.push(s);    while(!q.empty()) {        x=q.front(),q.pop(),inq[x]=0;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {            dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;            liu[to[i]]=min(liu[x],flow[i]);            if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);        }    }    if(!pre[t]) return 0;    x=t;    while(x!=s) {        int kl=pre[x];        flow[kl]-=liu[t],flow[kl^1]+=liu[t],x=to[kl^1];    }    return 1;}void print(int x,int pos) {//输出方案    if(x==n*m*2) return;    for(int i=h[x];i;i=h[x]) {        if(flow[i^1]>0&&to[i]<=n*m) {//如果这条边被车走过了            printf("%d ",pos);            if(to[i]==x-n*m+1) puts("1");            else puts("0");            --flow[i^1],print(to[i]+n*m,pos);            return;        }        h[x]=ne[i];    }}int main(){    int x,tmp;    scanf("%d%d%d",&car,&m,&n);    s=0,t=n*m*2+1;    add(s,1,car,0),add(n*m+n*m,t,car,0);    for(int i=1;i<=n;++i)        for(int j=1;j<=m;++j) scanf("%d",&ma[i][j]);    for(int i=1;i<=n;++i)        for(int j=1;j<=m;++j) {        tmp=(i-1)*m+j;        if(ma[i][j]==1) continue;        if(ma[i][j]==2) add(tmp,tmp+n*m,1,-1);        add(tmp,tmp+n*m,inf,0);        if(j!=m&&ma[i][j+1]!=1) add(tmp+n*m,tmp+1,inf,0);        if(i!=n&&ma[i+1][j]!=1) add(tmp+n*m,tmp+m,inf,0);    }    while(bfs());    for(int i=1;i<=car;++i) print(n*m+1,i);    return 0;}

24.骑士共存问题

也是一个最大独立点集的问题,参见第9题。
不过这题的最后一个测试点很BT,要在dinic跑dfs的时候判断,如果在某个点上的流量等于0了,这次增广就不要再到这个点来了(也就是代码注明部分)

#include<bits/stdc++.h>using namespace std;const int N=200*200+5,M=200*200*30+5,inf=0x3f3f3f3f;int n,m,s,t,tot=1,ans;int mvx[10]={0,-2,-1,1,2},mvy[10]={0,1,2,2,1};int ok[205][205],h[N],ne[M],to[M],flow[M],lev[N],q[N];void add(int x,int y,int z) {    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;}int dfs(int x,int liu) {    if(x==t) return liu;    int kl,sum=0;    for(int i=h[x];i;i=ne[i])        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {        kl=dfs(to[i],min(liu-sum,flow[i]));        flow[i]-=kl,flow[i^1]+=kl,sum+=kl;        if(sum==liu) return sum;    }    if(!sum) lev[x]=-1;//look at here. this is a 优化    return sum;}int bfs() {    for(int i=s;i<=t;++i) lev[i]=0;    int he=1,ta=1,x;lev[s]=1,q[1]=s;    while(he<=ta) {        x=q[he],++he;        if(x==t) return 1;        for(int i=h[x];i;i=ne[i])            if(flow[i]>0&&!lev[to[i]])            lev[to[i]]=lev[x]+1,q[++ta]=to[i];    }    return 0;}int main(){    int x,y;    scanf("%d%d",&n,&m);s=0,t=n*n+1;    for(int i=1;i<=m;++i) scanf("%d%d",&x,&y),ok[x][y]=1;    for(int i=1;i<=n;++i)        for(int j=1;j<=n;++j) {        if(ok[i][j]) continue;        if((i+j)&1) add(s,(i-1)*n+j,1);        else add((i-1)*n+j,t,1);        for(int k=1;k<=4;++k) {            int tx=i+mvx[k],ty=j+mvy[k];            if(tx<1||tx>n||ty<1||ty>n||ok[tx][ty]) continue;            if((i+j)&1) add((i-1)*n+j,(tx-1)*n+ty,inf);            else add((tx-1)*n+ty,(i-1)*n+j,inf);        }    }    while(bfs()) ans+=dfs(s,inf);    printf("%d",n*n-m-ans);    return 0;}