啊,学渣苦,学渣累
emm…
顾名思义,最小流的前提是有下界(不需要黑曜石)
emm…好问题。
我们需要先构建一个无源汇可行流
emm…
我们先这样定义一个网络图:
名称 | 意义 |
---|---|
E | 边集 |
G | 点集 |
B(i,j) | i→j 的下界 |
C(i,j) | i→j 的上界 |
C′(i,j) | i→j 的上界减下界 |
g(i,j) | i→j 的可行流量差值 |
f(i,j) | i→j 的实际流量 |
推导:首先被满足的是一个等式——流量平衡条件:
以及如何表示 f(i,j) :
那么说明在有下界限制条件下每个节点均能满足流量守恒,则 C′ 的一个可行流 g 一定对应的是原图中的一个可行流 f
然后你还可以判一判这个网络究竟有没有满足下界条件的可行流
(虽然很多OI题并没有打算让你干这个)
好所以我们现在来考虑如何求最小流
如果我们在加了附加源汇的图中,连一条 T0 到 S0 的边,设下界为 g(S0,T0) ,那么当前图就是一个循环流量图了
然后你也可以通过二分附加边的权值来确定这个图的最大可行流
什么?最小流怎么办?当然是二分上界辣
这个的复杂度是要带 log 的
所以我们考虑可行流的流量显然是边 (T0,S0) 的反向边的流量
那么接下来怎么做呢?
如果是求最大流你顺着 S0,T0 再跑一次好像就得了(???)
如果是最小流该怎么办?
对于最小流,考虑可行流可能多流了 S0 到 T0 的部分流量,我们反着跑最大流即可
什么?直接退流可能会使流不满足下界?
我们将第一次跑流时超级源点和超级汇点连出的边及其反向边都删掉,并且再删掉 (T0,S0) 这条边,这样保证流不会顺着这些边退回去导致不合法。
(1)BZOJ2502 清理雪道
滑雪场坐落在FJ省西北部的若干座山上。
从空中鸟瞰,滑雪场可以看作一个有向无环图,每条弧代表一个斜坡(即雪道),弧的方向代表斜坡下降的方向。
你的团队负责每周定时清理雪道。你们拥有一架直升飞机,每次飞行可以从总部带一个人降落到滑雪场的某个地点,然后再飞回总部。从降落的地点出发,这个人可以顺着斜坡向下滑行,并清理他所经过的雪道。
由于每次飞行的耗费是固定的,为了最小化耗费,你想知道如何用最少的飞行次数才能完成清理雪道的任务。
有向图求最小路径数,可以覆盖所有边至少一次
直接套用上述做法即可
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 111
#define sten 1111111
int ans,S,T,n,m,k,s,t,x,y,tot1,tot2;
int tot,first[sten],nxt[sten],goal[sten],wide[sten],M[stan];
int level[sten];
inline void addedge(int a,int b,int c){
nxt[++tot]=first[a];first[a]=tot;goal[tot]=b;wide[tot]=c;
nxt[++tot]=first[b];first[b]=tot;goal[tot]=a;wide[tot]=0;
return ;
}
bool bfs(){
static int que[sten];
int q=1;
for(int i=0;i<=n+3;++i)
level[i]=-1;
level[S]=1;que[q]=S;
for(int i=1;i<=q;++i){
int u=que[i];
for(int p=first[u];p;p=nxt[p])
if(wide[p]&&level[goal[p]]==-1){
level[goal[p]]=level[u]+1;
que[++q]=goal[p];
if(goal[p]==T) return true;
}
}
return false;
}
int dfs(int u,int flow){
if(u==T) return flow;
int ret=0,data;
for(int p=first[u];p;p=nxt[p])
if(wide[p]&&level[goal[p]]==level[u]+1){
data=dfs(goal[p],min(flow-ret,wide[p]));
if(data){
wide[p]-=data;
wide[p^1]+=data;
ret+=data;
if(ret==flow) return ret;
}
}
level[u]=-1;
return ret;
}
int solve(){
int ret=0;
while(bfs())
ret+=dfs(S,999999999);
return ret;
}
signed main(){
n=read();
s=0;t=n+1;
S=n+2;T=n+3;tot=1;
for(int i=1;i<=n;++i){
x=read();
addedge(s,i,999999999);
addedge(i,t,999999999);
for(int j=1;j<=x;++j){
y=read();
addedge(i,y,999999999-1);
--M[i];++M[y];
}
}
for(int i=1;i<=n;++i){
if(M[i]>0) addedge(S,i,M[i]);
if(M[i]<0) addedge(i,T,-M[i]);
}
addedge(t,s,999999999);
solve();
for(int p=first[S];p;p=nxt[p])
wide[p]=wide[p^1]=0;
for(int p=first[T];p;p=nxt[p])
wide[p]=wide[p^1]=0;
ans=wide[tot];
wide[tot-1]=wide[tot]=0;
S=t;T=s;
ans-=solve();
write(ans);
return 0;
}
(2)BZOJ1458 士兵占领
有一个M * N的棋盘,有的格子是障碍。现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵。我们称这些士兵占领了整个棋盘当满足第i行至少放置了Li个士兵, 第j列至少放置了Cj个士兵。现在你的任务是要求使用最少个数的士兵来占领整个棋盘。
注意有的边下界是0
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
inline void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 222
#define sten 1111111
int ans,S,T,n,m,k,s,t,x,y,tot1,tot2;
int tot,first[sten],nxt[sten],goal[sten],wide[sten];
int level[sten];
bool ac[stan][stan];
inline void addedge(int a,int b,int c){
nxt[++tot]=first[a];first[a]=tot;goal[tot]=b;wide[tot]=c;
nxt[++tot]=first[b];first[b]=tot;goal[tot]=a;wide[tot]=0;
return ;
}
bool bfs(){
static int que[sten];
int q=1;
for(int i=0;i<=T;++i)
level[i]=-1;
level[S]=1;que[q]=S;
for(int i=1;i<=q;++i){
int u=que[i];
for(int p=first[u];p;p=nxt[p])
if(wide[p]&&level[goal[p]]==-1){
level[goal[p]]=level[u]+1;
que[++q]=goal[p];
if(goal[p]==T) return true;
}
}
return false;
}
int dfs(int u,int flow){
if(u==T) return flow;
int ret=0,data;
for(int p=first[u];p;p=nxt[p])
if(wide[p]&&level[goal[p]]==level[u]+1){
data=dfs(goal[p],min(flow-ret,wide[p]));
if(data){
wide[p]-=data;
wide[p^1]+=data;
ret+=data;
if(ret==flow) return ret;
}
}
level[u]=-1;
return ret;
}
int solve(){
int ret=0;
while(bfs())
ret+=dfs(S,999999999);
return ret;
}
signed main(){
m=read();n=read();k=read();
S=n+m+2;T=n+m+3;tot=1;
s=0;t=n+m+1;
for(int i=1;i<=m;++i){
x=read();
addedge(s,i,999999999);
addedge(S,i,x);
tot1+=x;
}
addedge(s,T,tot1);
for(int i=1;i<=n;++i){
x=read();
addedge(m+i,t,999999999);
addedge(m+i,T,999999999);
tot2+=x;
}
addedge(t,S,tot2);
for(int i=1;i<=k;++i){
x=read();y=read();
ac[x][y]=true;
}
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j)
if(!ac[i][j])
addedge(i,j+m,1);
addedge(S,t,tot2);
ans=solve();
if(ansputs("JIONG");
return 0;
}else{
for(int p=first[S];p;p=nxt[p])
wide[p]=wide[p^1]=0;
for(int p=first[T];p;p=nxt[p])
wide[p]=wide[p^1]=0;
S=t;T=s;
ans-=solve();
write(ans);
return 0;
}
return 0;
}