网络流24题解题总结(更新中)
目录
前言
从今天(2018.1.19)起,本人要在这篇文章中持续更新网络流24题的解法。
注意,本人这里贴的做法和代码都以洛谷上的题目版本为准,可能与其他OJ的版本略有出入,所以请自行判断。
为了讨论方便,我们约定以下符号:
:源点和汇点。
:表示从到连一条容量为,费用为的边。有时费用为时省略。
:表示从到连一条流量下界为,上界为,费用为的边。有时费用为时省略。
那么,现在就开始吧。
最大流问题
飞行员配对方案问题
更新时间: 2018.1.19
测试地址:飞行员配对方案问题
做法:本题需要用到最大流。
这个题目是很经典的二分图最大匹配的模型了,用匈牙利算法也可以做,但既然出现在网络流24题里,那么就用最大流做即可。具体做法应该不用多说,从到所有外籍飞行员连边,从所有英国飞行员到连边,再在可以配合的飞行员之间连边(从外籍到英国),容量都为,然后跑最大流即可。
至于输出方案,显然可知,在我们连的正向边中满流的边就是匹配边,所以一个循环搞定。
以下是本人代码:
#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[b],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;
}
最小路径覆盖问题
更新时间: 2018.1.20
测试地址:最小路径覆盖问题
做法:本题需要用到最大流。
求有向无环图的最小路径覆盖,可以转化为二分图最大匹配的模型求解。将每个点拆成两个点,然后如果原图存在有向边->,则在二分图中连->。注意到,对于该二分图的任意一个匹配,将所有缩成一个点后,就等价于一个路径覆盖,而且覆盖的路径条数点数匹配数。那么显然,当匹配数最大时,覆盖的路径条数自然就最小,那么我们就得到了一个最小的路径覆盖。因此只用对上面构造出的二分图求最大匹配,可以用最大流解决。输出方案应该也很容易,只需要从路径的开头沿着匹配边走就可以了。
以下是本人代码:
#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[b],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;
}
魔术球问题
更新时间: 2018.1.20
测试地址:魔术球问题
做法:本题需要用到最大流。
我们想办法把该题化成我们见过的模型解决。我们很快想到,需要从小到大枚举球的个数,然后化为判定性问题:能不能用不超过根柱子放下这些球?可以看出,球和如果能放在一起,那么就相当于存在有向边->,那么一根柱子就相当于一条路径,因此只需对这样构造出的有向图求一个最小路径覆盖,如果路径数不超过就表示能放。求最小路径覆盖我们就很熟悉了,分析详见上面一题。
以下是本人代码:
#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[b],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;
}
圆桌问题
更新时间: 2018.1.20
测试地址:圆桌问题
做法:本题需要用到最大流。
本题是一个经典的二分图多重匹配的模型,显然对于每个单位连接,对于每个餐桌连接,又因为每个餐桌不能坐两个或者以上的同一个单位的代表,所以对于单位和餐桌之间应该连接,建完之后跑最大流即可。输出方案的方法和前面的二分图最大匹配差不多,不同的是这里有无解的情况,如果最大流不等于所有单位代表人数总和的话,显然是无解的,其余的输出方法详见代码。
以下是本人代码:
#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[b],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;
}
最长不下降子序列问题
更新时间: 2018.1.21
测试地址:最长不下降子序列问题
做法:本题需要用到DP+最大流。
对于第一问,我们直接DP求出答案即可,将答案记为。
对于第二问,令为以第个元素结尾的最长不下降子序列的长度,这个东西在上一步DP时就已经求出来了,接下来我们这样建图:如果两点满足关系:且且,则连有向边->。注意到,从任意一个的点,沿这些有向边走到一个的点,中间的路径上经过的点就是一个长为的最长不下降子序列,那么问题就转化为:有若干个起点和终点,求最多能有多少条不相交的从起点到终点的路径?这个模型就可以用网络流的方法解决了。
首先,因为每个点只能出现一次,所以有“点流量”的限制,这里每个点的限制都是。一般遇到这种情况,我们可以将一个点拆成两个点,并将原点的入边都连到,将原点的出边都连到,然后连接,这里显然,这样就可以解决点流量限制的问题。剩下的部分应该挺容易想出来了,对于所有的点,连接,对于所有的点,连接,对于原图中所有的有向边->,对应连接,然后对这个网络跑一次最大流就是第二问的答案。
对于第三问,第和第个元素可以出现多次,那么我们只需将原网络中的四条边的容量改为,然后再跑一次最大流就是第三问的答案。
以下是本人代码:
#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 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[b],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;
}
试题库问题
更新时间: 2018.1.21
测试地址:试题库问题
做法:本题需要用到最大流。
本题也是经典的二分图多重匹配的模型,不过不像圆桌问题那样可以随便匹配了,每个类型只对应一部分题目。那么显然,对于所有类型,连接,对于所有题目,连接(因为每道题目只能用一次),如果题目包含类型,那么连接,对这个网络跑一遍最大流,然后照和圆桌问题类似的输出方案的方法输出即可。
以下是本人代码:
#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[b],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;
}
星际转移问题
更新时间: 2018.1.24
测试地址:星际转移问题
做法:本题需要用到最大流。
由于最终的天数不确定,所以我们枚举答案,转化为判定性问题:天内人能不能全部到达月球?对于每一天,建个点,表示这一天的地球、个太空站和月球,然后建模就比较容易了:
对于每一天的地球,连接;
对于每一天的月球,连接;
对于所有飞船,如果前一天在点,当天在点,那么连接;
对于每个点,连接(人留在原地等的情况)。
这样一来,只要当天的最大流达到了,就说明当天可以有个人到达月球了。因为我们是从小到大枚举答案,所以我们可以在每一次做完的残余网络上再加新边再跑,这样会跑的快些(大概吧)。
最后还有一个问题,判断无解的问题。网上有人直接估算可能达到的最大天数,这样有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[b],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;
}
最小割问题
太空飞行计划问题
更新时间: 2018.1.20
测试地址:太空飞行计划问题
做法:本题需要用到最小割。
本题是一个经典的求最大权闭合子图的模型。所谓闭合子图,是指求一个顶点集,使得该顶点集中任意一个点的出边都指向该顶点集中的点,而最大权闭合子图就是在所有闭合子图中求一个点权和最大的。因此,最大权闭合子图的模型常用来解决一些有依赖关系的选取问题,构图方法是:如果选就必须选,那么连接->。经过一些证明(具体证明看这里),可以得到一个用最小割的思想求解该问题的模型:
对于原图中所有的边->,连接,接下来对于所有正权点,连接,对于所有负权点,连接。然后对于这个网络求最小割(也就等同于求最大流),记作,在最小割分成的两个顶点集中,所在的集合为所求,而最大权和就是所有正权点权值和减去。
至于输出方案,求完最大流之后,从通过残余网络能到达的所有点就是所求的答案,虽然我不太会证明割边一定满流,有待学习。
以下是本人代码:
#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[b],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;
}
方格取数问题
更新时间: 2018.1.21
测试地址:方格取数问题
做法:本题需要用到最小割。
将每个格子看做点,然后将每个点和上下左右四个点连边,显然地,因为该图不存在奇环,所以网格图是一个二分图。那么问题就转化为求二分图的最大点权和独立集,这时我们想办法将其转化为我们更加熟悉的模型解决。
我们试探性地往图中加入源点和汇点,然后对于二分图的其中一部分,从到所有该部分中的点连边,从所有另一部分中的点到连边,边权为该点对应的格子上数的值,接下来将原二分图中所有的边权设为,方向设为从连接的点指向连接的点,我们来看看这个图有什么性质。可以看出,每一条从到的路径都对应了一对不能同时取的点,所以这些路径都不能存在,因此我们需要去掉一些边使得从不能到达,这里我们只会去掉边权不为的边,因此每去掉一条边都相当于放弃了与这条边相连的那个点。而我们又要使得剩下的点的点权和最大,也就意味着剩下的边权不为的边权和最大,也就是删掉的边的边权和最小,这显然就是一个最小割了。所以我们按照上述方法建一个网络,容量即为边权,然后跑一遍最大流,因为最大流等于最小割,所以用所有数的和减去得到的结果就是最后的答案。
以下是本人代码:
#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[b],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;
}
费用流问题
餐巾计划问题
更新时间: 2018.1.19
测试地址:餐巾计划问题
做法:本题需要用到费用流。
将本题的模型简化一下,可以知道第天的入度来源于:新购买的餐巾,前一天没用完的干净餐巾,刚快洗或慢洗完的餐巾,而出度都指向第天(快洗)和第天(慢洗),而且要求点流量必须大于等于。那么显然我们把代表一天的点拆成两个点,并连接。根据上下界网络流的解法(实际上是看大佬题解抄来的),这条边等价于。剩下的建模就很简单了,只需要连(相当于把所有要买的餐巾一上来先买好),(快洗)和(慢洗),然后跑个正常的费用流即可。
以下是本人代码:
#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[b],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;
}
航空路线问题
更新时间: 2018.1.23
测试地址:航空路线问题
做法:本题需要用到费用流。
首先,题目求的是一个环,包含从西向东和从东向西两条不相交(除起点和终点)的路径,显然可以看做两条从西向东的两条不相交(除起点和终点)路径来求。因为每个城市只能经过一次,所以有点流量限制,因此我们把一个点拆成两个点,并在中间连接,显然对于除起点和终点的其他城市,点流量限制为,否则为。其他部分就比较简单了,先连接和,再将原图的无向边改成从西向东的有向边->,因为我们拆了点,所以实际连的是。再然后,因为要求经过的点最多,所以跑最大费用最大流即可,只需将SPFA中求最短路改成求最长路即可。特殊地,如果最大流小于则无解。至于输出方案,应该不难,但是要注意细节。
以下是本人代码:
#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[b],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[n]);
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;
}
软件补丁问题
更新时间: 2018.1.23
测试地址:软件补丁问题
做法:本题需要用到费用流。
不难想到,将错误的个状态全部存储成点,然后把“使用补丁”这一过程看作状态的转移,用位运算把图建出来,最后从到的最短路径就是答案。没错,一次最短路就能解决的事情为什么要放在网络流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[b],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;
}
数字梯形问题
更新时间: 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[b],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;
}
运输问题
更新时间: 2018.1.25
测试地址:运输问题
做法:本题需要用到费用流。
做了那么多题的你看到这样的题,应该很快就能想出建模方法了:
对于所有仓库,连接;
对于所有商店,连接;
对于所有运输方法->,连接。
对上面的网络,分别跑一次最小费用最大流和最大费用最大流即可。
以下是本人代码:
#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[b],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;
}
分配问题
更新时间: 2018.1.25
测试地址:分配问题
做法:本题需要用到费用流。
本题是经典的二分图最佳匹配模型,可以用KM算法做,当然也可以用费用流来解。建图的方法和上面那题基本一样,只不过从源点流出和进入汇点的边容量都是,建完图后分别跑一次最小费用最大流和最大费用最大流即可。
以下是本人代码:
#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[b],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;
}
负载平衡问题
更新时间: 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[b],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;
}
深海机器人问题
更新时间: 2018.1.26
测试地址:深海机器人问题
做法:本题需要用到费用流。
这题唯一比较难的点是如何处理每条边只能取一次,但其实也不难,只需要先连,然后再连一个即可,其他建图应该比较容易了,建完之后跑最大费用最大流即可。
以下是本人代码:
#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[b],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可重区间集问题
更新时间: 2018.1.26
测试地址:最长k可重区间集问题
做法:本题需要用到费用流。
尝试建一个有向图:将一个区间看做一个点,点权为该区间的长度,若两个区间不相交,且在之前,则连接->。不难看出,问题可以转化为在这个有向图中选取条不相交的路径,使得路径上的点权总和最大。因为每个点都有可能是起点或者终点,所以从向所有点、从所有点向连边,而又因为题目只限制了总流量,所以应该将拆成两个点,然后连接。题目要求路径不相交,那么每个点肯定只能经过一次,因此将每个点拆成两个点,然后连接。其他的边就按照原有向图的边连即可,容量为,费用为,建完图后跑一遍最大费用最大流即可。
(据说还有另一种建模方式,有待学习)
以下是本人代码:
#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[b],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可重线段集问题
更新时间: 2018.1.27
测试地址:最长k可重线段集问题
做法:本题需要用到费用流。
本题的做法和上面的最长k可重区间集差不多,区别有:
1.线段长度的计算不同了(…废话),注意在洛谷上必须要开long long才不会爆。
2.不同于区间,开区间的左右端点不可能是一个数,但是开线段的两个端点的横坐标可能相同,也就是垂直于轴,解决方法是:将所有横坐标乘以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[b],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;
}
不可做问题
机器人路径规划问题
更新时间: 2018.1.21
测试地址:机器人路径规划问题
做法:本题是这24题中唯一一道不能用网络流算法解决的问题,需要用一堆诡异的随机化算法解决,但据说有大牛弄出来了的DP做法,这里只附个链接供大家膜拜:
大佬 Orz <-我