您的位置:首页 > 理论基础 > 计算机网络

网络流24题解题总结(更新中)

2018-01-19 11:25 453 查看

目录

目录

前言

最大流问题
飞行员配对方案问题

最小路径覆盖问题

魔术球问题

圆桌问题

最长不下降子序列问题

试题库问题

星际转移问题

最小割问题
太空飞行计划问题

方格取数问题

费用流问题
餐巾计划问题

航空路线问题

软件补丁问题

数字梯形问题

运输问题

分配问题

负载平衡问题

深海机器人问题

最长k可重区间集问题

最长k可重线段集问题

不可做问题
机器人路径规划问题

前言

从今天(2018.1.19)起,本人要在这篇文章中持续更新网络流24题的解法。

注意,本人这里贴的做法和代码都以洛谷上的题目版本为准,可能与其他OJ的版本略有出入,所以请自行判断。

为了讨论方便,我们约定以下符号:

S,TS,T:源点和汇点。

(u,v,maxf,c)(u,v,maxf,c):表示从uu到vv连一条容量为maxfmaxf,费用为cc的边。有时费用为00时省略cc。

<u,v,minf,maxf,c><u,v,minf,maxf,c>:表示从uu到vv连一条流量下界为minfminf,上界为maxfmaxf,费用为cc的边。有时费用为00时省略cc。

那么,现在就开始吧。

最大流问题

飞行员配对方案问题

更新时间: 2018.1.19

测试地址:飞行员配对方案问题

做法:本题需要用到最大流。

这个题目是很经典的二分图最大匹配的模型了,用匈牙利算法也可以做,但既然出现在网络流24题里,那么就用最大流做即可。具体做法应该不用多说,从SS到所有外籍飞行员连边,从所有英国飞行员到TT连边,再在可以配合的飞行员之间连边(从外籍到英国),容量都为11,然后跑最大流即可。

至于输出方案,显然可知,在我们连的正向边中满流的边就是匹配边,所以一个循环搞定。

以下是本人代码:

#include <bits/stdc++.h>
#define inf 1000000000
using namespace std;
int m,n,a,b,first[210]={0},tot=1,lvl[210];
struct edge {int v,next,f;} e[100010];
queue <int> Q;

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[0]=0;
for(int i=1;i<=m+n+1;i++)
lvl[i]=-1;
Q.push(0);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[m+n+1]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==m+n+1) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(0,inf);
return maxf;
}

int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++) insert(0,i,1);
for(int i=1;i<=n;i++) insert(m+i,m+n+1,1);
while(scanf("%d%d",&a,&b)&&a!=-1) insert(a,b,1);

printf("%d\n",dinic());
for(int i=1;i<=m;i++)
for(int j=first[i];j;j=e[j].next)
if (!e[j].f&&j%2==0) printf("%d %d\n",i,e[j].v);

return 0;
}


最小路径覆盖问题

[b]更新时间:
2018.1.20

测试地址:最小路径覆盖问题

做法:本题需要用到最大流。

求有向无环图的最小路径覆盖,可以转化为二分图最大匹配的模型求解。将每个点拆成两个点xi,yixi,yi,然后如果原图存在有向边ii->jj,则在二分图中连yiyi->xjxj。注意到,对于该二分图的任意一个匹配,将所有xi,yixi,yi缩成一个点后,就等价于一个路径覆盖,而且覆盖的路径条数==点数−−匹配数。那么显然,当匹配数最大时,覆盖的路径条数自然就最小,那么我们就得到了一个最小的路径覆盖。因此只用对上面构造出的二分图求最大匹配,可以用最大流解决。输出方案应该也很容易,只需要从路径的开头沿着匹配边走就可以了。

以下是本人代码:

#include<bits/stdc++.h>
#define inf 1000000000
using namespace std;
int n,m,first[310]={0},tot=1,lvl[310]={0},path[160]={0};
struct edge {int v,next,f;} e[100010];
queue <int> Q;
bool vis[160]={0},in[160]={0};

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[0]=0;
for(int i=1;i<=2*n+1;i++)
lvl[i]=-1;
Q.push(0);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[2*n+1]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==2*n+1) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(0,inf);
return maxf;
}

void output()
{
for(int i=1;i<=n;i++)
for(int j=first[2*i-1];j;j=e[j].next)
if (j%2==0&&!e[j].f) in[e[j].v/2]=1;
for(int i=1;i<=n;i++)
if (!in[i])
{
path[0]=0;
int x=2*i-1;
bool flag=1;
while(flag)
{
flag=0;
path[++path[0]]=(x+1)/2;
vis[(x+1)/2]=1;
for(int j=first[x];j;j=e[j].next)
if (j%2==0&&!e[j].f) {x=e[j].v-1;flag=1;break;}
}
for(int j=path[0];j>=1;j--) printf("%d ",path[j]);
printf("\n");
}
for(int i=first[0];i;i=e[i].next)
if (!vis[(e[i].v+1)/2]) printf("%d\n",(e[i].v+1)/2);
}

int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(2*b-1,2*a,1);
}
for(int i=1;i<=n;i++)
{
insert(0,2*i-1,1);
insert(2*i,2*n+1,1);
}

int ans=n-dinic();
output();
printf("%d",ans);

return 0;
}


魔术球问题

[b]更新时间:
2018.1.20

测试地址:魔术球问题

做法:本题需要用到最大流。

我们想办法把该题化成我们见过的模型解决。我们很快想到,需要从小到大枚举球的个数,然后化为判定性问题:能不能用不超过nn根柱子放下这些球?可以看出,球ii和j(i<j)j(i<j)如果能放在一起,那么就相当于存在有向边ii->jj,那么一根柱子就相当于一条路径,因此只需对这样构造出的有向图求一个最小路径覆盖,如果路径数不超过nn就表示能放。求最小路径覆盖我们就很熟悉了,分析详见上面一题。

以下是本人代码:

#include<bits/stdc++.h>
#define inf 1000000000
#define s 0
#define t 4009
using namespace std;
int n,m,first[4010]={0},tot=1,lvl[4010]={0},saved=0;
struct edge {int v,next,f;} e[400010];
queue <int> Q;
bool vis[2010]={0},in[2010]={0};

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[s]=0;
for(int i=1;i<=m;i++)
lvl[i]=-1;
lvl[t]=-1;
Q.push(0);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&(e[i].v<=m||e[i].v==t)&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==t) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&(e[i].v<=m||e[i].v==t)&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=saved;
while(makelevel()) maxf+=maxflow(0,inf);
saved=maxf;
return maxf;
}

void output()
{
for(int i=1;i<=m/2;i++)
for(int j=first[2*i-1];j;j=e[j].next)
if (j%2==0&&!e[j].f) in[e[j].v/2]=1;
for(int i=1;i<=m/2;i++)
if (!in[i]&&!vis[i])
{
int x=2*i-1;
bool flag=1;
while(flag)
{
flag=0;
printf("%d ",(x+1)/2);
vis[(x+1)/2]=1;
for(int j=first[x];j;j=e[j].next)
if (j%2==0&&!e[j].f) {x=e[j].v-1;flag=1;break;}
}
printf("\n");
}
for(int i=first[0];i;i=e[i].next)
if (!vis[(e[i].v+1)/2]&&e[i].v<=m) printf("%d\n",(e[i].v+1)/2);
}

bool check(int now)
{
m=2*now;
insert(s,2*now-1,1);
insert(2*now,t,1);
int j=1;
for(int i=1;i<now;i++)
{
while(j*j<i+now) j++;
if (j*j==i+now) insert(2*i-1,2*now,1);
}
int ans=now-dinic();
return ans<=n;
}

int main()
{
scanf("%d",&n);

int i=1;
while(check(i)) i++;

i--;
m=2*i;
saved=0;
for(int i=2;i<=tot;i+=2)
e[i].f=1,e[i+1].f=0;
dinic();
printf("%d\n",i);
output();

return 0;
}


圆桌问题

[b]更新时间:
2018.1.20

测试地址:圆桌问题

做法:本题需要用到最大流。

本题是一个经典的二分图多重匹配的模型,显然对于每个单位ii连接(S,i,ri)(S,i,ri),对于每个餐桌jj连接(j,T,cj)(j,T,cj),又因为每个餐桌不能坐两个或者以上的同一个单位的代表,所以对于单位ii和餐桌jj之间应该连接(i,j,1)(i,j,1),建完之后跑最大流即可。输出方案的方法和前面的二分图最大匹配差不多,不同的是这里有无解的情况,如果最大流不等于所有单位代表人数总和的话,显然是无解的,其余的输出方法详见代码。

以下是本人代码:

#include <bits/stdc++.h>
#define s 0
#define t m+n+1
#define inf 1000000000
using namespace std;
int m,n,sum=0,first[510]={0},tot=1,lvl[510];
struct edge {int v,next,f;} e[100010];
queue <int> Q;

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[s]=0;
for(int i=1;i<=t;i++) lvl[i]=-1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==t) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(s,inf);
return maxf;
}

void output()
{
for(int i=1;i<=m;i++)
{
for(int j=first[i];j;j=e[j].next)
if (j%2==0&&!e[j].f) printf("%d ",e[j].v-m);
printf("\n");
}
}

int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
{
int a;
scanf("%d",&a);
sum+=a;
insert(s,i,a);
}
for(int i=1;i<=n;i++)
{
int a;
scanf("%d",&a);
insert(m+i,t,a);
}
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
insert(i,m+j,1);

if (dinic()!=sum) printf("0");
else
{
printf("1\n");
output();
}

return 0;
}


最长不下降子序列问题

[b]更新时间:
2018.1.21

测试地址:最长不下降子序列问题

做法:本题需要用到DP+最大流。

对于第一问,我们直接O(n2)O(n2)DP求出答案即可,将答案记为KK。

对于第二问,令f(i)f(i)为以第ii个元素结尾的最长不下降子序列的长度,这个东西在上一步DP时就已经求出来了,接下来我们这样建图:如果两点i,ji,j满足关系:i<ji<j且ai≤ajai≤aj且f(i)+1=f(j)f(i)+1=f(j),则连有向边ii->jj。注意到,从任意一个f(i)=1f(i)=1的点,沿这些有向边走到一个f(i)=Kf(i)=K的点,中间的路径上经过的点就是一个长为KK的最长不下降子序列,那么问题就转化为:有若干个起点和终点,求最多能有多少条不相交的从起点到终点的路径?这个模型就可以用网络流的方法解决了。

首先,因为每个点只能出现一次,所以有“点流量”的限制,这里每个点的限制都是11。一般遇到这种情况,我们可以将一个点拆成两个点u,vu,v,并将原点的入边都连到uu,将原点的出边都连到vv,然后连接(u,v,maxf)(u,v,maxf),这里显然maxf=1maxf=1,这样就可以解决点流量限制的问题。剩下的部分应该挺容易想出来了,对于所有f(i)=1f(i)=1的点,连接(S,ui,1)(S,ui,1),对于所有f(i)=Kf(i)=K的点,连接(vi,T,1)(vi,T,1),对于原图中所有的有向边ii->jj,对应连接(vi,uj,1)(vi,uj,1),然后对这个网络跑一次最大流就是第二问的答案。

对于第三问,第11和第nn个元素可以出现多次,那么我们只需将原网络中的(S,u1,1),(u1,v1,1),(un,vn,1),(vn,T,1)(S,u1,1),(u1,v1,1),(un,vn,1),(vn,T,1)四条边的容量改为infinf,然后再跑一次最大流就是第三问的答案。

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t 2*n+1
#define inf 1000000000
using namespace std;
int n,a[510],f[510],mx=0,first[1010]={0},tot=1,lvl[1010];
struct edge {int<
b0c8
/span> v,next,f;} e[200010];
queue <int> Q;

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[s]=0;
for(int i=1;i<=t;i++) lvl[i]=-1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==t) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(0,inf);
return maxf;
}

int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[i]=1;
for(int j=1;j<i;j++)
if (a[j]<=a[i]) f[i]=max(f[i],f[j]+1);
for(int j=1;j<i;j++)
if (a[j]<=a[i]&&f[i]==f[j]+1)
insert(2*j,2*i-1,1);
mx=max(mx,f[i]);
}
for(int i=1;i<=n;i++)
{
insert(2*i-1,2*i,1);
if (f[i]==1) insert(s,2*i-1,1);
if (f[i]==mx) insert(2*i,t,1);
}

printf("%d\n",mx);
printf("%d\n",dinic());

for(int i=2;i<=tot;i+=2)
e[i].f=1,e[i+1].f=0;
for(int i=first[s];i;i=e[i].next)
if (i%2==0&&e[i].v==1) {e[i].f=inf;break;}
for(int i=first[1];i;i=e[i].next)
if (i%2==0&&e[i].v==2) {e[i].f=inf;break;}
for(int i=first[2*n-1];i;i=e[i].next)
if (i%2==0&&e[i].v==2*n) {e[i].f=inf;break;}
for(int i=first[2*n];i;i=e[i].next)
if (i%2==0&&e[i].v==t) {e[i].f=inf;break;}

printf("%d",dinic());

return 0;
}


试题库问题

[b]更新时间:
2018.1.21

测试地址:试题库问题

做法:本题需要用到最大流。

本题也是经典的二分图多重匹配的模型,不过不像圆桌问题那样可以随便匹配了,每个类型只对应一部分题目。那么显然,对于所有类型ii,连接(S,i,pi)(S,i,pi),对于所有题目ii,连接(i,T,1)(i,T,1)(因为每道题目只能用一次),如果题目jj包含类型ii,那么连接(i,j,1)(i,j,1),对这个网络跑一遍最大流,然后照和圆桌问题类似的输出方案的方法输出即可。

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t m+n+1
#define inf 1000000000
using namespace std;
int m,n,sum=0,first[1510]={0},tot=1,lvl[1510];
struct edge {int v,next,f;} e[200010];
queue <int> Q;

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[s]=0;
for(int i=1;i<=t;i++) lvl[i]=-1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==t) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(0,inf);
return maxf;
}

void output()
{
for(int i=1;i<=m;i++)
{
printf("%d:",i);
for(int j=first[i];j;j=e[j].next)
if (!e[j].f&&j%2==0&&e[j].v>m) printf(" %d",e[j].v-m);
printf("\n");
}
}

int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
{
int p;
scanf("%d",&p);
insert(s,i,p);
sum+=p;
}
for(int i=1;i<=n;i++)
{
int p,a;
scanf("%d",&p);
while(p--)
{
scanf("%d",&a);
insert(a,m+i,1);
}
insert(m+i,t,1);
}

if (dinic()!=sum) printf("No Solution!");
else output();

return 0;
}


星际转移问题

[b]更新时间:
2018.1.24

测试地址:星际转移问题

做法:本题需要用到最大流。

由于最终的天数不确定,所以我们枚举答案,转化为判定性问题:ansans天内人能不能全部到达月球?对于每一天,建n+2n+2个点,表示这一天的地球、nn个太空站和月球,然后建模就比较容易了:

对于每一天的地球,连接(S,earthi,inf)(S,earthi,inf);

对于每一天的月球,连接(mooni,T,inf)(mooni,T,inf);

对于所有飞船,如果前一天在点xx,当天在点yy,那么连接(xi−1,yi,h)(xi−1,yi,h);

对于每个点xx,连接(xi−1,xi,inf)(xi−1,xi,inf)(人留在原地等的情况)。

这样一来,只要当天的最大流达到了kk,就说明当天可以有kk个人到达月球了。因为我们是从小到大枚举答案,所以我们可以在每一次做完的残余网络上再加新边再跑,这样会跑的快些(大概吧)。

最后还有一个问题,判断无解的问题。网上有人直接估算可能达到的最大天数,这样有WA或TLE的风险,最稳的方法还是并查集判断连通性,详见代码。

以下是本人代码:

#include<bits/stdc++.h>
#define inf 1000000000
#define s 0
#define t 10009
using namespace std;
int n,m,k,first[20010]={0},tot=1,lvl[20010]={0},saved=0,tim=1;
int h[25],r[25],p[25][25],fa[25];
struct edge {int v,next,f;} e[1000010];
queue <int> Q;

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[s]=0;
for(int i=1;i<=(tim+1)*(n+2);i++)
lvl[i]=-1;
lvl[t]=-1;
Q.push(0);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==t) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=saved;
while(makelevel()) maxf+=maxflow(0,inf);
saved=maxf;
return maxf;
}

bool check()
{
for(int i=0;i<=n+1;i++)
insert((tim-1)*(n+2)+i+1,tim*(n+2)+i+1,inf);
insert(s,(tim-1)*(n+2)+1,inf);
insert(tim*(n+2)+n+2,t,inf);
for(int i=1;i<=m;i++)
{
int x=p[i][tim%r[i]],y=p[i][(tim-1+r[i])%r[i]];
insert((tim-1)*(n+2)+y+1,tim*(n+2)+x+1,h[i]);
}
dinic();
return saved>=k;
}

int ufs_find(int x)
{
int r=x,i=x,j;
while(fa[r]!=r) r=fa[r];
while(i!=r) j=fa[i],fa[i]=r,i=j;
return r;
}

void ufs_merge(int x,int y)
{
int fx=ufs_find(x),fy=ufs_find(y);
if (fx!=fy) fa[fx]=fy;
}

int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<=n+1;i++) fa[i]=i;

for(int i=1;i<=m;i++)
{
scanf("%d%d",&h[i],&r[i]);
for(int j=0;j<r[i];j++)
{
scanf("%d",&p[i][j]);
if (p[i][j]==-1) p[i][j]=n+1;
if (j>0) ufs_merge(p[i][j],p[i][j-1]);
}
}

if (ufs_find(0)!=ufs_find(n+1)) {printf("0");return 0;}

while(!check()) tim++;
printf("%d",tim);

return 0;
}


最小割问题

太空飞行计划问题

[b]更新时间:
2018.1.20

测试地址:太空飞行计划问题

做法:本题需要用到最小割。

本题是一个经典的求最大权闭合子图的模型。所谓闭合子图,是指求一个顶点集,使得该顶点集中任意一个点的出边都指向该顶点集中的点,而最大权闭合子图就是在所有闭合子图中求一个点权和最大的。因此,最大权闭合子图的模型常用来解决一些有依赖关系的选取问题,构图方法是:如果选aa就必须选bb,那么连接aa->bb。经过一些证明(具体证明看这里),可以得到一个用最小割的思想求解该问题的模型:

对于原图中所有的边uu->vv,连接(u,v,inf)(u,v,inf),接下来对于所有正权点uu,连接(S,u,valueu)(S,u,valueu),对于所有负权点vv,连接(v,T,|valuev|)(v,T,|valuev|)。然后对于这个网络求最小割(也就等同于求最大流),记作ff,在最小割分成的两个顶点集中,SS所在的集合为所求,而最大权和就是所有正权点权值和减去ff。

至于输出方案,求完最大流之后,从SS通过残余网络能到达的所有点就是所求的答案,虽然我不太会证明割边一定满流,有待学习。

以下是本人代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll inf=1000000000,sum=0;
int m,n,first[110]={0},tot=1,lvl[110]={0};
struct edge {int v,next;ll f;} e[100010];
queue <int> Q;

int read(bool &flag)
{
char c;
int s=0;
c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();
flag=(c=='\n'||c=='\r');
return s;
}

void insert(int a,int b,ll f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[0]=0;
for(int i=1;i<=m+n+1;i++)
lvl[i]=-1;
Q.push(0);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[m+n+1]!=-1;
}

ll maxflow(int v,ll maxf)
{
if (v==m+n+1) return maxf;
ll ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

ll dinic()
{
ll maxf=0;
while(makelevel()) maxf+=maxflow(0,inf);
return maxf;
}

int main()
{
inf*=inf;

scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
{
ll f;
bool flag=0;
scanf("%lld",&f);
sum+=f;
insert(0,i,f);
while(true)
{
int s=read(flag);
insert(i,m+s,inf);
if (flag) break;
}
}
for(int i=1;i<=n;i++)
{
ll f;
scanf("%lld",&f);
insert(m+i,m+n+1,f);
}

ll ans=sum-dinic();
for(int i=1;i<=m;i++)
if (lvl[i]!=-1) printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++)
if (lvl[i+m]!=-1) printf("%d ",i);
printf("\n%lld",ans);

return 0;
}


方格取数问题

[b]更新时间:
2018.1.21

测试地址:方格取数问题

做法:本题需要用到最小割。

将每个格子看做点,然后将每个点和上下左右四个点连边,显然地,因为该图不存在奇环,所以网格图是一个二分图。那么问题就转化为求二分图的最大点权和独立集,这时我们想办法将其转化为我们更加熟悉的模型解决。

我们试探性地往图中加入源点和汇点,然后对于二分图的其中一部分,从SS到所有该部分中的点连边,从所有另一部分中的点到TT连边,边权为该点对应的格子上数的值,接下来将原二分图中所有的边权设为infinf,方向设为从连接SS的点指向连接TT的点,我们来看看这个图有什么性质。可以看出,每一条从SS到TT的路径都对应了一对不能同时取的点,所以这些路径都不能存在,因此我们需要去掉一些边使得从SS不能到达TT,这里我们只会去掉边权不为infinf的边,因此每去掉一条边都相当于放弃了与这条边相连的那个点。而我们又要使得剩下的点的点权和最大,也就意味着剩下的边权不为infinf的边权和最大,也就是删掉的边的边权和最小,这显然就是一个最小割了。所以我们按照上述方法建一个网络,容量即为边权,然后跑一遍最大流,因为最大流等于最小割,所以用所有数的和减去得到的结果就是最后的答案。

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t m*n+1
#define inf 1000000000
using namespace std;
int m,n,sum=0,first[10010]={0},tot=1,lvl[10010];
struct edge {int v,next,f;} e[200010];
queue <int> Q;

void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
lvl[s]=0;
for(int i=1;i<=t;i++) lvl[i]=-1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
Q.push(e[i].v);
}
}
return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
if (v==t) return maxf;
int ret=0,f;
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(e[i].f,maxf-ret));
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) break;
}
return ret;
}

int dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(0,inf);
return maxf;
}

int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
int a;
scanf("%d",&a);
sum+=a;
if ((i+j)%2==0)
{
if (i>1) insert((i-1)*n+j,(i-2)*n+j,inf);
if (i<m) insert((i-1)*n+j,i*n+j,inf);
if (j>1) insert((i-1)*n+j,(i-1)*n+j-1,inf);
if (j<n) insert((i-1)*n+j,(i-1)*n+j+1,inf);
insert(s,(i-1)*n+j,a);
}
else insert((i-1)*n+j,t,a);
}

printf("%d",sum-dinic());

return 0;
}


费用流问题

餐巾计划问题

[b]更新时间:
2018.1.19

测试地址:餐巾计划问题

做法:本题需要用到费用流。

将本题的模型简化一下,可以知道第ii天的入度来源于:新购买的餐巾,前一天没用完的干净餐巾,刚快洗或慢洗完的餐巾,而出度都指向第i+mi+m天(快洗)和第i+ni+n天(慢洗),而且要求点流量必须大于等于riri。那么显然我们把代表一天的点拆成两个点u,vu,v,并连接<u,v,ri,inf><u,v,ri,inf>。根据上下界网络流的解法(实际上是看大佬题解抄来的),这条边等价于(S,v,ri)+(u,T,ri)+(u,v,inf)(S,v,ri)+(u,T,ri)+(u,v,inf)。剩下的建模就很简单了,只需要连(S,u1,inf,p)(S,u1,inf,p)(相当于把所有要买的餐巾一上来先买好),(vi,ui+m,inf,f)(vi,ui+m,inf,f)(快洗)和(vi,ui+n,inf,s)(vi,ui+n,inf,s)(慢洗),然后跑个正常的费用流即可。

以下是本人代码:

#include <bits/stdc++.h>
#define ll long long
#define inf 1000000000
using namespace std;
int N,first[5010]={0},tot=1,laste[5010],last[5010];
ll p,m,f,n,s,dis[5010];
bool vis[5010]={0};
struct edge {ll v,f,c,next;} e[200010];
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
ll s;
while(!Q.empty()) Q.pop();
Q.push(0);
memset(vis,0,sizeof(vis));
dis[0]=0;vis[0]=1;
for(int i=1;i<=2*N+1;i++)
dis[i]=inf;
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[v]+e[i].c<dis[e[i].v])
{
dis[e[i].v]=dis[v]+e[i].c;
laste[e[i].v]=i;
last[e[i].v]=v;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[2*N+1]!=inf;
}

ll mincost()
{
ll ans=0;
while(spfa())
{
int x=2*N+1;
ll minf=inf;
while(x) minf=min(minf,e[laste[x]].f),x=last[x];
x=2*N+1;
while(x) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=minf*dis[2*N+1];
}
return ans;
}

int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
ll r;
scanf("%lld",&r);
insert(0,2*i,r,0);
insert(2*i-1,2*N+1,r,0);
insert(2*i-1,2*i,inf,0);
}
scanf("%lld%lld%lld%lld%lld",&p,&m,&f,&n,&s);

insert(0,1,inf,p);
for(int i=1;i<=N;i++)
{
if (i<N) insert(2*i-1,2*i+1,inf,0);
if (i+m<=N) insert(2*i,2*(i+m)-1,inf,f);
if (i+n<=N) insert(2*i,2*(i+n)-1,inf,s);
}

printf("%lld",mincost());

return 0;
}


航空路线问题

[b]更新时间:
2018.1.23

测试地址:航空路线问题

做法:本题需要用到费用流。

首先,题目求的是一个环,包含从西向东和从东向西两条不相交(除起点和终点)的路径,显然可以看做两条从西向东的两条不相交(除起点和终点)路径来求。因为每个城市只能经过一次,所以有点流量限制,因此我们把一个点拆成两个点ui,viui,vi,并在中间连接(ui,vi,maxf,1)(ui,vi,maxf,1),显然对于除起点和终点的其他城市,点流量限制为11,否则为22。其他部分就比较简单了,先连接(S,u1,2)(S,u1,2)和(vn,T,inf)(vn,T,inf),再将原图的无向边改成从西向东的有向边ii->jj,因为我们拆了点,所以实际连的是(vi,uj,inf)(vi,uj,inf)。再然后,因为要求经过的点最多,所以跑最大费用最大流即可,只需将SPFA中求最短路改成求最长路即可。特殊地,如果最大流小于22则无解。至于输出方案,应该不难,但是要注意细节。

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t 2*n+1
#define inf 1000000000
using namespace std;
int n,m,first[210]={0},tot=1,dis[210],last[210],laste[210],maxf,path[210];
char name[110][20],names[20];
struct edge {int v,next,f,c;} e[100010];
bool vis[210]={0};
queue <int> Q;

void insert(int a,int b,int f,int c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
dis[s]=0,vis[s]=1;
for(int i=1;i<=t;i++)
dis[i]=-inf;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
{
dis[e[i].v]=dis[v]+e[i].c;
last[e[i].v]=v;
laste[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
}
vis[v]=0;
}
return dis[t]!=-inf;
}

int maxcost()
{
int ans=0;
maxf=0;
while(spfa())
{
int x=t,minf=inf;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=dis[t]*minf;
maxf+=minf;
}
return ans;
}

void output()
{
int x,i;
bool flag=0;
printf("%s\n",name[1]);
for(i=first[2];i;i=e[i].next)
if ((e[i].f==inf-1||e[i].f==inf-2)&&i%2==0) break;
x=e[i].v;
while(x!=2*n-1)
{
printf("%s\n",name[(x+1)/2]);
x++;
for(int j=first[x];j;j=e[j].next)
if (e[j].f==inf-1&&j%2==0) {x=e[j].v;break;}
}
printf("%s\n",name
);
if (e[i].f!=inf-2)
{
i=e[i].next;
for(;i;i=e[i].next)
if (e[i].f==inf-1&&i%2==0) break;
x=e[i].v;
path[0]=0;
while(x!=2*n-1)
{
path[++path[0]]=(x+1)/2;
x++;
for(int j=first[x];j;j=e[j].next)
if (e[j].f==inf-1&&j%2==0) {x=e[j].v;break;}
}
for(i=path[0];i>=1;i--) printf("%s\n",name[path[i]]);
}
printf("%s",name[1]);
}

int find()
{
for(int i=1;i<=n;i++)
{
bool flag=1;
int len=max(strlen(name[i]),strlen(names));
for(int j=0;j<len;j++)
if (name[i][j]!=names[j]) {flag=0;break;}
if (flag) return i;
}
}

int main()
{
scanf("%d%d",&n,&m);

insert(s,1,2,0);
insert(2*n,t,inf,0);
for(int i=1;i<=n;i++)
{
if (i==1||i==n) insert(2*i-1,2*i,2,1);
else insert(2*i-1,2*i,1,1);
scanf("%s",name[i]);
}
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%s",names);
a=find();
scanf("%s",names);
b=find();
if (a>b) swap(a,b);
insert(2*a,2*b-1,inf,0);
}

int ans=maxcost()-2;
if (maxf<2) printf("No Solution!");
else
{
printf("%d\n",ans);
output();
}

return 0;
}


软件补丁问题

[b]更新时间:
2018.1.23

测试地址:软件补丁问题

做法:本题需要用到费用流。

不难想到,将错误的2n2n个状态全部存储成点,然后把“使用补丁”这一过程看作状态的转移,用位运算把图建出来,最后从(111...1)(111...1)到(000...0)(000...0)的最短路径就是答案。没错,一次最短路就能解决的事情为什么要放在网络流24题里呢……虽然这样,但为了达到练习(方便Ctrl+C和Ctrl+V)的效果,我的代码还是用费用流的模板写的。

然而,要注意的是,如果我们在做之前就把图建好,结果是无论如何都会MLE或者RE,这时我们就要动态加边,就是等到用到时才把边建出来,我也不知道为什么加了这个之后就能过了,大概是玄学吧……

以下是本人代码:

#include <bits/stdc++.h>
#define s 0
#define t (1<<n)+1
#define ll long long
#define inf 1000000000
using namespace std;
int n,m,first[2000010]={0},tot=1,laste[2000010],last[2000010];
int b1[110]={0},b2[110]={0},f1[110]={0},f2[110]={0};
ll dis[2000010],tim[110];
bool vis[2000010]={0},visit[2000010]={0};
struct edge {int v,next;ll f,c;} e[2000010];
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
Q.push(s);
dis[s]=0;vis[s]=1;
for(int i=1;i<=t;i++)
dis[i]=inf;
while(!Q.empty())
{
int v=Q.front();Q.pop();
if (v&&!visit[v])
{
v--;
for(int i=1;i<=m;i++)
if ((v&b1[i])==b1[i]&&(v&b2[i])==0)
insert(v+1,((v-(v&f1[i]))|f2[i])+1,1,tim[i]);
v++;
visit[v]=1;
}
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[v]+e[i].c<dis[e[i].v])
{
dis[e[i].v]=dis[v]+e[i].c;
laste[e[i].v]=i;
last[e[i].v]=v;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[t]!=inf;
}

ll mincost()
{
ll ans=0;
while(spfa())
{
int x=t;
ll minf=inf;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=minf*dis[t];
}
return ans;
}

int main()
{
scanf("%d%d",&n,&m);
insert(s,1<<n,1,0);
insert(1,t,1,0);
for(int i=1;i<=m;i++)
{
char b[20],f[20];
scanf("%lld%s%s",&tim[i],b,f);
for(int j=0;j<n;j++)
{
if (b[j]=='+') b1[i]+=(1<<j);
if (b[j]=='-') b2[i]+=(1<<j);
if (f[j]=='-') f1[i]+=(1<<j);
if (f[j]=='+') f2[i]+=(1<<j);
}
}

printf("%lld",mincost());

return 0;
}


数字梯形问题

[b]更新时间:
2018.1.25

测试地址:数字梯形问题

做法:本题需要用到费用流。

对于这道题,因为每次只能往左下和右下两个数字走,所以边相交的情况实际上只有重边的情况,所以直接考虑网络流建模。

对于第一问,相当于限制了点容量和往下走的边容量,因此惯例把每个点拆成两个点即可,剩下的建模就直接按照转移图建就行。

对于第二问,相当于只限制了往下走的边容量,这时要注意,从最下面一行到汇点的边不属于限制边,因为有可能出现路径在最后一行相交的情况。这时我们只需把点容量限制取消即可。

对于第三问,相当于没有限制,取消所有限制即可。

按照上述方式建完图后,在点容量的边上加一个费用,即这个点上数的值,然后跑最大费用最大流即可。

以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t 509
#define inf 1000000000
using namespace std;
int m,n,p=0,first[1010]={0},tot=1;
int last[1010],laste[1010];
ll dis[1010];
struct edge {int v,next,type;ll f,c;} e[200010];
queue <int> Q;
bool vis[1010]={0};

void insert(int a,int b,ll f,ll c,int type)
{
e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].type=type,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].type=type,e[tot].next=first,first[b]=tot;
}

bool spfa()
{
dis[s]=0;
for(int i=1;i<=2*p;i++) dis[i]=-inf;
dis[t]=-inf;
vis[s]=1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
{
laste[e[i].v]=i;
last[e[i].v]=v;
dis[e[i].v]=dis[v]+e[i].c;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[t]>0;
}

ll maxcost()
{
ll ans=0,minf;
while(spfa())
{
minf=inf;
int x=t;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=minf*dis[t];
}
return ans;
}

int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=m+i-1;j++)
{
ll a;
scanf("%lld",&a);
p++;
if (i==1) insert(s,2*p-1,1,0,0);
if (i==n) insert(2*p,t,inf,0,1);
insert(2*p-1,2*p,1,a,1);
if (i>1&&j>1) insert(2*(p-m-i+1),2*p-1,1,0,2);
if (i>1&&j<m+i-1) insert(2*(p-m-i+2),2*p-1,1,0,2);
}

printf("%lld\n",maxcost());
for(int i=2;i<=tot;i+=2)
{
if (e[i].type==1) e[i].f=inf;
else e[i].f=1;
e[i^1].f=0;
}
printf("%lld\n",maxcost());
for(int i=2;i<=tot;i+=2)
{
if (e[i].type>0) e[i].f=inf;
else e[i].f=1;
e[i^1].f=0;
}
printf("%lld",maxcost());

return 0;
}


运输问题

[b]更新时间:
2018.1.25

测试地址:运输问题

做法:本题需要用到费用流。

做了那么多题的你看到这样的题,应该很快就能想出建模方法了:

对于所有仓库ii,连接(S,i,ai,0)(S,i,ai,0);

对于所有商店jj,连接(j,T,bj,0)(j,T,bj,0);

对于所有运输方法ii->jj,连接(i,j,inf,cij)(i,j,inf,cij)。

对上面的网络,分别跑一次最小费用最大流和最大费用最大流即可。

以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t m+n+1
using namespace std;
int m,n,first[210]={0},tot=1;
int last[210],laste[210];
ll dis[210],inf;
struct edge {int v,next;ll f,c;} e[200010];
queue <int> Q;
bool vis[210]={0};

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first,first[b]=tot;
}

bool spfa(bool type)
{
dis[s]=0;
for(int i=1;i<=t;i++) dis[i]=type?inf:-inf;
vis[s]=1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&((!type&&dis[e[i].v]<dis[v]+e[i].c)||(type&&dis[e[i].v]>dis[v]+e[i].c)))
{
laste[e[i].v]=i;
last[e[i].v]=v;
dis[e[i].v]=dis[v]+e[i].c;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[t]!=(type?inf:-inf);
}

ll mincost(bool type)
{
ll ans=0,minf;
while(spfa(type))
{
minf=inf;
int x=t;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=minf*dis[t];
}
return ans;
}

int main()
{
inf=1000000000;
inf*=inf;

scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
{
ll a;
scanf("%lld",&a);
insert(s,i,a,0);
}
for(int i=1;i<=n;i++)
{
ll a;
scanf("%lld",&a);
insert(m+i,t,a,0);
}
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
ll a;
scanf("%lld",&a);
insert(i,m+j,inf,a);
}

printf("%lld\n",mincost(1));
for(int i=2;i<=tot;i+=2)
e[i].f=e[i].f+e[i^1].f,e[i^1].f=0;
printf("%lld",mincost(0));

return 0;
}


分配问题

[b]更新时间:
2018.1.25

测试地址:分配问题

做法:本题需要用到费用流。

本题是经典的二分图最佳匹配模型,可以用KM算法做,当然也可以用费用流来解。建图的方法和上面那题基本一样,只不过从源点流出和进入汇点的边容量都是11,建完图后分别跑一次最小费用最大流和最大费用最大流即可。

以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t 2*n+1
using namespace std;
int n,first[210]={0},tot=1;
int last[210],laste[210];
ll dis[210],inf;
struct edge {int v,next;ll f,c;} e[200010];
queue <int> Q;
bool vis[210]={0};

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first,first[b]=tot;
}

bool spfa(bool type)
{
dis[s]=0;
for(int i=1;i<=t;i++) dis[i]=type?inf:-inf;
vis[s]=1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&((!type&&dis[e[i].v]<dis[v]+e[i].c)||(type&&dis[e[i].v]>dis[v]+e[i].c)))
{
laste[e[i].v]=i;
last[e[i].v]=v;
dis[e[i].v]=dis[v]+e[i].c;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[t]!=(type?inf:-inf);
}

ll mincost(bool type)
{
ll ans=0,minf;
while(spfa(type))
{
minf=inf;
int x=t;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=minf*dis[t];
}
return ans;
}

int main()
{
inf=1000000000;
inf*=inf;

scanf("%d",&n);
for(int i=1;i<=n;i++)
{
insert(s,i,1,0);
insert(n+i,t,1,0);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
ll a;
scanf("%lld",&a);
insert(i,n+j,inf,a);
}

printf("%lld\n",mincost(1));
for(int i=2;i<=tot;i+=2)
e[i].f=e[i].f+e[i^1].f,e[i^1].f=0;
printf("%lld",mincost(0));

return 0;
}


负载平衡问题

[b]更新时间:
2018.1.25

测试地址:负载平衡问题

做法:本题需要用到费用流。

虽然这题貌似可以用贪心或者中位数啥啥的做法水过,但是这题的数据范围明显适用网络流,而且我们很快就能想出建模的方法。

对于每个仓库,建立一个供给节点和一个需求节点,对于这道题,每个仓库的供给量就是一开始的货物数,需求量就是总货物数/仓库数,而一份货物从一个仓库转移到另一个仓库需要一些代价。看到这里,其实建模思路已经非常明显了,按照运输问题那样的建模方式建,然后跑最小费用最大流即可。

以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t 2*n+1
#define inf 1000000000
using namespace std;
int n,p=0,first[210]={0},tot=1;
int last[210],laste[210];
ll dis[210],sum=0;
struct edge {int v,next;ll f,c;} e[200010];
queue <int> Q;
bool vis[210]={0};

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first,first[b]=tot;
}

bool spfa()
{
dis[s]=0;
for(int i=1;i<=t;i++) dis[i]=inf;
vis[s]=1;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]>dis[v]+e[i].c)
{
laste[e[i].v]=i;
last[e[i].v]=v;
dis[e[i].v]=dis[v]+e[i].c;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[t]!=inf;
}

ll mincost()
{
ll ans=0,minf;
while(spfa())
{
minf=inf;
int x=t;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=minf*dis[t];
}
return ans;
}

int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
ll a;
scanf("%lld",&a);
sum+=a;
insert(s,i,a,0);
}
for(int i=1;i<=n;i++) insert(n+i,t,sum/n,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
insert(i,n+j,inf,min(abs(i-j),n-abs(i-j)));

printf("%lld",mincost());

return 0;
}


深海机器人问题

[b]更新时间:
2018.1.26

测试地址:深海机器人问题

做法:本题需要用到费用流。

这题唯一比较难的点是如何处理每条边只能取一次,但其实也不难,只需要先连(u,v,1,x)(u,v,1,x),然后再连一个(u,v,inf,0)(u,v,inf,0)即可,其他建图应该比较容易了,建完之后跑最大费用最大流即可。

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t (p+1)*(q+1)+1
#define ll long long
using namespace std;
int p,q,a,b,first[510]={0},tot=1,last[510],laste[510];
ll dis[510],inf;
struct edge {int v,next;ll f,c;} e[200010];
bool vis[510]={0};
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
dis[s]=0,vis[s]=1;
for(int i=1;i<=t;i++)
dis[i]=-inf;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
{
dis[e[i].v]=dis[v]+e[i].c;
last[e[i].v]=v;
laste[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
}
vis[v]=0;
}
return dis[t]!=-inf;
}

ll maxcost()
{
ll ans=0;
while(spfa())
{
int x=t;
ll minf=inf;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=dis[t]*minf;
}
return ans;
}

int main()
{
inf=1000000000;
inf*=inf;

scanf("%d%d%d%d",&a,&b,&p,&q);
for(int i=0;i<p+1;i++)
for(int j=0;j<q;j++)
{
ll x;
scanf("%lld",&x);
insert(j*(p+1)+i+1,(j+1)*(p+1)+i+1,1,x);
insert(j*(p+1)+i+1,(j+1)*(p+1)+i+1,inf,0);
}
for(int j=0;j<q+1;j++)
for(int i=0;i<p;i++)
{
ll x;
scanf("%lld",&x);
insert(j*(p+1)+i+1,j*(p+1)+i+2,1,x);
insert(j*(p+1)+i+1,j*(p+1)+i+2,inf,0);
}
for(int i=1;i<=a;i++)
{
ll k;int x,y;
scanf("%lld%d%d",&k,&x,&y);
swap(x,y);
insert(s,x*(p+1)+y+1,k,0);
}
for(int i=1;i<=b;i++)
{
ll k;int x,y;
scanf("%lld%d%d",&k,&x,&y);
swap(x,y);
insert(x*(p+1)+y+1,t,k,0);
}

printf("%lld",maxcost());

return 0;
}


最长k可重区间集问题

[b]更新时间:
2018.1.26

测试地址:最长k可重区间集问题

做法:本题需要用到费用流。

尝试建一个有向图:将一个区间看做一个点,点权为该区间的长度,若两个区间i,ji,j不相交,且ii在jj之前,则连接ii->jj。不难看出,问题可以转化为在这个有向图中选取kk条不相交的路径,使得路径上的点权总和最大。因为每个点都有可能是起点或者终点,所以从SS向所有点、从所有点向TT连边,而又因为题目只限制了总流量,所以应该将SS拆成两个点,然后连接(S1,S2,k)(S1,S2,k)。题目要求路径不相交,那么每个点肯定只能经过一次,因此将每个点拆成两个点,然后连接(xi,yi,1,valuei)(xi,yi,1,valuei)。其他的边就按照原有向图的边连即可,容量为infinf,费用为00,建完图后跑一遍最大费用最大流即可。

(据说还有另一种建模方式,有待学习)

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define ss 2*n+2
#define t 2*n+1
#define ll long long
using namespace std;
int n,k,first[1010]={0},tot=1,last[1010],laste[1010];
ll l[1010],r[1010],dis[1010],inf;
struct edge {int v,next;ll f,c;} e[500010];
bool vis[1010]={0};
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
dis[s]=0,vis[s]=1;
for(int i=1;i<=ss;i++)
dis[i]=-inf;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
{
dis[e[i].v]=dis[v]+e[i].c;
last[e[i].v]=v;
laste[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
}
vis[v]=0;
}
return dis[t]!=-inf;
}

ll maxcost()
{
ll ans=0;
while(spfa())
{
int x=t;
ll minf=inf;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=dis[t]*minf;
}
return ans;
}

int main()
{
inf=1000000000;
inf*=inf;

scanf("%d%d",&n,&k);
insert(s,ss,k,0);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&l[i],&r[i]);
insert(ss,2*i-1,inf,0);
insert(2*i,t,inf,0);
insert(2*i-1,2*i,1,r[i]-l[i]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if (l[j]>=r[i]) insert(2*i,2*j-1,inf,0);

printf("%lld",maxcost());

return 0;
}


最长k可重线段集问题

[b]更新时间:
2018.1.27

测试地址:最长k可重线段集问题

做法:本题需要用到费用流。

本题的做法和上面的最长k可重区间集差不多,区别有:

1.线段长度的计算不同了(…废话),注意在洛谷上必须要开long long才不会爆。

2.不同于区间,开区间的左右端点不可能是一个数,但是开线段的两个端点的横坐标可能相同,也就是垂直于xx轴,解决方法是:将所有横坐标乘以2,如果一条线段两端点的横坐标相同,那么右端点++,否则左端点++,这样就可以将左右端点隔开了。

其他建模方法和上一题相同,详见代码。

以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define ss 2*n+2
#define t 2*n+1
#define ll long long
using namespace std;
int n,k,first[1010]={0},tot=1,last[1010],laste[1010];
ll xz[1010],yz[1010],xo[1010],yo[1010],dis[1010],inf;
struct edge {int v,next;ll f,c;} e[500010];
bool vis[1010]={0};
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first,e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
dis[s]=0,vis[s]=1;
for(int i=1;i<=ss;i++)
dis[i]=-inf;
Q.push(s);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
{
dis[e[i].v]=dis[v]+e[i].c;
last[e[i].v]=v;
laste[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
}
vis[v]=0;
}
return dis[t]!=-inf;
}

ll maxcost()
{
ll ans=0;
while(spfa())
{
int x=t;
ll minf=inf;
while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
x=t;
while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
ans+=dis[t]*minf;
}
return ans;
}

int main()
{
inf=1000000000;
inf*=inf;

scanf("%d%d",&n,&k);
insert(s,ss,k,0);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld%lld%lld",&xz[i],&yz[i],&xo[i],&yo[i]);
if (xo[i]<xz[i]) swap(xz[i],xo[i]),swap(yz[i],yo[i]);
insert(ss,2*i-1,inf,0);
insert(2*i,t,inf,0);
insert(2*i-1,2*i,1,(ll)sqrt((xo[i]-xz[i])*(xo[i]-xz[i])+(yo[i]-yz[i])*(yo[i]-yz[i])));
xz[i]<<=1,xo[i]<<=1;
if (xz[i]==xo[i]) xo[i]++;
else xz[i]++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if (xz[j]>=xo[i]) insert(2*i,2*j-1,inf,0);

printf("%lld",maxcost());

return 0;
}


不可做问题

机器人路径规划问题

[b]更新时间:
2018.1.21

测试地址:机器人路径规划问题

做法:本题是这24题中唯一一道不能用网络流算法解决的问题,需要用一堆诡异的随机化算法解决,但据说有大牛弄出来了O(n6)O(n6)的DP做法,这里只附个链接供大家膜拜:

大佬 Orz <-我
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: