题解 P5192 【Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流】
Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流解题报告
标签: 解题报告
Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流解题报告:
可能更好的阅读体验
先吐槽一下,这虽然是板子题,但看懂怎么建模还要一小会儿
还有,为什么要把三倍经验合并啊
题意与分析
题意:一共有照骗照片,有照骗数量不得少于
我们很容易从标题:【模板】有源汇上下界最大流
无源汇上下界可行流
我们考虑一个问题:在一张图中,每条边有流量下界和流量上界,求是否存在一种方案在满足流量平衡的情况下,使所有边满足上下界限制。
注意:这里的流量平衡指:对于所有点
很容易想到一种想法,我们把上限减去下限,然后跑最大流。但是这个很显然是不满足的,因为经过操作后可能会形成这样的图:,这张图是很显然没有可行流的。但是经过操作后,原图会变为:,此时存在可行流。
我们发现答案前后不一的原因是经过这种操作后新图不满足流量平衡了,这就很麻烦了,跑不了最大流,而且也不能随便发明一个不需要流量平衡的最大流算法,因此我们想想如何经过玄学操作是新图满足流量平衡。
对了!原点和汇点还在旁边晾着,我们要让它们干活。因为最大流算法中源点和汇点可以不满足流量平衡,因此我们可以把锅推到源点和汇点上,同样是把每条边上限减去下限,但是对于每个点,我们计算
简单来说,所有连向
因此,这个步骤已经很明了了:
- 统计每个点
x 的in_x 与out_x ,即连向x 的边的流量之和与连出x 的边的流量之和。 - 对于每一条原图里的边,流量设为上限减去下限,因此得到一张新图。
- 虚拟源点与汇点
s 和t ,对于所有的x ,如果in_x>out_x ,则从s 向x 连一条in_x-out_x 的边,否则从x 向t 连一条out_x-in_x 的边。 - 跑一遍从
s 到t 的最大流,如果s 连出的边可以满流(由于流量平衡,等价于连向t 的边可以满流),证明存在可行流。
有源汇上下界可行流
考虑有源点和汇点的上下界可行流,即在无源汇上下界可行流的基础上增加两个点不满足流量守恒。
由于流量平衡流出
因此,步骤为:
- 从原图中的汇点向源点连一条上限为
inf 的边。 - 跑一遍无源汇上下界可行流。
有源汇上下界最大流
接下来,我们看有源汇的上下界最大流。我们并不满足于求可行流,在找可行流的基础上,还想找最大流,这样应该怎么办呢?
我们先找到一个可行流
但是这个可行流不一定是最大流,因为可行流只会跑满与虚拟源点和虚拟汇点相连的边,因此
因此我们考虑计算
此时
注意一点,在跑原图的最大流时需要删去从原图汇点到原图源点的边,如果这条边没有删去,则会导致死循环。
但是与虚拟源点和虚拟汇点相连的边可以不删去,因为在求第一次最大流的时候就已经将这些边“榨干”了(因为是满流),否则根本不存在可行流。
步骤:
- 跑出一个有源汇上下界可行流,如果满流则设其答案为
flow1 ,否则不存在答案。 - 删去从原图汇点到原图源点的边。
- 跑从原图源点到原图汇点的最大流,设答案为
flow2 ,则总答案ans=flow1+flow2 。
回归题目
让我们回归题目,很容易可以看出来题目的建模方式:先虚拟一个源点
然后就按照有源汇上下界最大流的建模继续就ok了。
代码
注意:少女的编号是从
#include<stdio.h>
#include<queue>
#include<string.h>
#define inf 2147483647
using namespace std;
const int maxn=1000005,maxm=1000005;
int i,j,k,m,n,s,t,s1,t1,s2,t2,e,flg,ans,sum,anss;
int start[maxn],to[maxm],then[maxm],worth[maxm],rev[maxm],dep[maxn],vis[maxn],in[maxn],out[maxn],G[maxn];
queue<int>q;
inline int min(int a,int b){
return a<b? a:b;
}
inline void add(int x,int y,int z,int r){
then[++e]=start[x],start[x]=e,to[e]=y,worth[e]=z,rev[e]=r;
}
inline void addedge(int x,int y,int z){
add(x,y,z,e+2),add(y,x,0,e);
}
void bfs(){
while(!q.empty())
q.pop();
for(int i=1;i<maxn;i++)
dep[i]=inf,vis[i]=0;
dep[s]=0;
q.push(s);
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=start[x];i;i=then[i]){
int y=to[i];
if(worth[i]&&dep[y]>dep[x]+1){
dep[y]=dep[x]+1;
if(vis[y]==0){
vis[y]=1;
q.push(y);
}
}
}
}
}
int dfs(int x,int flw){
if(x==t){
flg=1,ans+=flw;
return flw;
}
int rest=flw;
for(int i=start[x];i;i=then[i]){
int y=to[i];
if(worth[i]&&dep[y]==dep[x]+1){
int newflw=dfs(y,min(rest,worth[i]));
if(newflw>0){
rest-=newflw;
worth[i]-=newflw,worth[rev[i]]+=newflw;
if(rest==0)
break;
}
else dep[y]=0;
}
}
return flw-rest;
}
int Dinic(){
ans=0;
while(1){
bfs();
if(dep[t]==inf)
break;
flg=1;
while(flg){
flg=0;
dfs(s,inf);
}
}
return ans;
}
int main(){
while(~scanf("%d%d",&n,&m)){
sum=e=0;
memset(start,0,sizeof(start));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
s1=n+m+1,t1=n+m+2,s2=n+m+3,t2=n+m+4;
s=s2,t=t2;
for(i=1;i<=m;i++){
scanf("%d",&G[i]);
in[t1]+=G[i],out[n+i]+=G[i];
addedge(n+i,t1,inf-G[i]);
}
for(i=1;i<=n;i++){
int C,D,T,L,R;
scanf("%d%d",&C,&D);
addedge(s1,i,D);
for(j=1;j<=C;j++){
scanf("%d%d%d",&T,&L,&R);
T++;
in[n+T]+=L,out[i]+=L;
addedge(i,n+T,R-L);
}
}
for(i=1;i<=n+m+2;i++){
if(in[i]>out[i])
addedge(s,i,in[i]-out[i]),sum+=in[i]-out[i];
else addedge(i,t,out[i]-in[i]);
}
addedge(t1,s1,inf);
if(Dinic()!=sum){
puts("-1\n");
continue;
}
anss=worth[e];
worth[e]=worth[rev[e]]=0;
s=s1,t=t1;
anss+=Dinic();
printf("%d\n\n",anss);
}
return 0;
}
扩展-有源汇上下界最小流
我们看到有源点和汇点的上下界最小流,即在可行流的基础上要求流量最小。
我们先跑一遍可行流,设可行流为
则最小流为可行流
其实我们可以发现还能向下浮动的流量就是从原图汇点到原图源点的最大流(感性理解)。
因此步骤为:
- 跑出一个有源汇上下界可行流,如果满流则设其答案为
flow1 ,否则不存在答案。 - 删去从原图汇点到原图源点的边。
- 跑从原图汇点到原图源点的最大流,设答案为
flow2 ,则总答案ans=flow1-flow2 。