最近看了胡伯涛的论文,最小割问题也去写了几道,那时候二月份学长提到的几道最小割题目那时听得一知半解现在重新去翻翻感觉有了新的想法,那时候不能理解的构图现在也都可以比较轻松的构造出来了,下面来总结几发吧。
[BZOJ 1797][AHOI2009]最小割 mincut
Description
A,B两个国家正在交战,其中A国的物资运输网中有N个中转站,M条单向道路。设其中第i (1≤i≤M)条道路连接了vi,ui两个中转站,那么中转站vi可以通过该道路到达ui中转站,如果切断这条道路,需要代价ci。现在B国想找出一个路径切断方案,使中转站s不能到达中转站t,并且切断路径的代价之和最小。 小可可一眼就看出,这是一个求最小割的问题。但爱思考的小可可并不局限于此。现在他对每条单向道路提出两个问题: 问题一:是否存在一个最小代价路径切断方案,其中该道路被切断? 问题二:是否对任何一个最小代价路径切断方案,都有该道路被切断? 现在请你回答这两个问题。
Analysis
先跑一遍最大流。
首先有一个结论,一个最小割里的边必定是满流边。试想,如果该边不是满流边,那么说明他还有剩余流量,必定是遭到了前面或者后面边的阻塞,删掉它显然不会是最优的,而应该删掉S到T路径上经过该点的那些满流边。
然后在残留网络中求一遍强连通分量,记录下每个点所在的强连通分量。
对于问题一,那么对于满流边的两个顶点X→Y,它们必定属于两个不同的强连通分量。因为最小割的定义是把S和T割开的最小代价。若X和Y属于同一个强连通分量,说明虽然X→Y这条边遭到了阻塞,但是X仍然有办法通过其他的路径到达Y。最小割是把两个点割开的最小代价,而删掉这条边无法将X,Y割开,所以删掉它必定不优,舍去这条边。而对于不在同一个强连通分量里的,说明删掉这条边X到达不了Y,可能在最小割集里。
对于问题二,对于一条满流边X→Y,X与S在同一个强连通分量,Y与T在同一个强连通分量,那么这条边必定在最小割集里。因为根据问题一的推断,X与S在同一个强连通分量,说明仍存在S到X的路径,同理存在Y到T的路径。删掉这条边是唯一可以保证将这几条路径隔断的方案,所以必定在最小割集里。
Code:
var
edge,i,x,y,c,n,m,vs,vt,sum,tot,num,ans:longint;
e,ee:array[1..120010,1..3] of longint;
d,vd,dfn,lowlink,stack,fr,papa:array[0..4000] of longint;
vis,instack:array[1..4000] of boolean;
procedure add(x,y,c:longint);
begin
inc(edge); e[edge,1]:=y; e[edge,2]:=fr[x]; e[edge,3]:=c; fr[x]:=edge;
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
function dfs(u,flow:longint):longint;
var
k,tmp:longint;
begin
if u=vt then exit(flow);
dfs:=0;
k:=fr[u];
while k<>0 do begin
if (e[k,3]>0) and (d[u]=d[e[k,1]]+1) then begin
tmp:=dfs(e[k,1],min(e[k,3],flow-dfs));
inc(dfs,tmp);
dec(e[k,3],tmp);
if odd(k) then inc(e[k+1,3],tmp) else inc(e[k-1,3],tmp);
if dfs=flow then exit;
end;
k:=e[k,2];
end;
dec(vd[d[u]]);
if vd[d[u]]=0 then d[vs]:=num;
inc(d[u]);
inc(vd[d[u]]);
end;
procedure dfs(v:longint);
var
k,u:longint;
begin
vis[v]:=true;
inc(sum); dfn[v]:=sum; lowlink[v]:=sum;
inc(tot); stack[tot]:=v; instack[v]:=true;
k:=fr[v];
while k<>0 do begin
u:=e[k,1];
if e[k,3]>0 then
if not vis[u] then begin
dfs(u);
lowlink[v]:=min(lowlink[v],lowlink[u]);
end
else
if instack[u] then
lowlink[v]:=min(lowlink[v],dfn[u]);
k:=e[k,2];
end;
if dfn[v]=lowlink[v] then begin
inc(num);
repeat
papa[stack[tot]]:=num;
instack[stack[tot]]:=false;
dec(tot);
until stack[tot+1]=v;
end;
end;
begin
readln(n,m,vs,vt);
num:=n; vd[0]:=num;
for i:=1 to m do begin
readln(x,y,c);
add(x,y,c);
add(y,x,0);
ee[i,1]:=x; ee[i,2]:=y; ee[i,3]:=c;
end;
while d[vs]<num do ans:=ans+dfs(vs,maxlongint);
for i:=1 to n do if not vis[i] then dfs(i);
for i:=1 to m do begin
if e[i+i-1,3]<>0 then begin writeln('0 0'); continue; end;
if papa[ee[i,1]]<>papa[ee[i,2]] then write(1,' ') else write(0,' ');
if (papa[ee[i,1]]=papa[vs]) and (papa[ee[i,2]]=papa[vt]) then writeln(1) else writeln(0);
end;
end.
下一道题:[BZOJ 2229][ZJOI 2011]最小割
吐槽:和刚才那一题名字是不一样的吧?
Description
小白在图论课上学到了一个新的概念——最小割,下课后小白在笔记本上写下了如下这段话: “对于一个图,某个对图中结点的划分将图中所有结点分成两个部分,如果结点s,t不在同一个部分中,则称这个划分是关于s,t的割。 对于带权图来说,将所有顶点处在不同部分的边的权值相加所得到的值定义为这个割的容量,而s,t的最小割指的是在关于s,t的割中容量最小的割” 现给定一张无向图,小白有若干个形如“图中有多少对点它们的最小割的容量不超过x呢”的疑问,小蓝虽然很想回答这些问题,但小蓝最近忙着挖木块,于是作为仍然是小蓝的好友,你又有任务了。
Analysis
这题也算神了吧...我也是看JZP的题解才会的。
首先随意取出两个点,求出它们的最小割。然后所有点被划分成了S集和T集,先用第一次求出的容量更新所有[U,V]的最小割容量,(U∈S,V∈T),然后将S和T集合分治处理,并用求出的答案全局更新。举个例子,对于分出来的S集合中再枚举两个点求出最小割,然后更新答案,这里求出来的S'集合要与这一次求出来的V'集合和上一层的V集更新答案。可以证明不同的最小割大小最多只有N种(证明见JZP博客)。也就只会更新N次。具体讲不清楚,上代码吧。
Code:
var
t,n,m,x,y,c,i,q,vs,vt,num,j,k:longint;
bg,g,cnt:array[0..151,0..151] of longint;
f,v:array[0..151] of boolean;
d,vd,query,ans:array[0..151] of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
function dfs(u,flow:longint):longint;
var
i,tmp:longint;
begin
if u=vt then exit(flow);
dfs:=0;
for i:=vs to vt do begin
if (g[u,i]>0) and (d[u]=d[i]+1) then begin
tmp:=dfs(i,min(g[u,i],flow-dfs));
inc(dfs,tmp);
dec(g[u,i],tmp);
inc(g[i,u],tmp);
if dfs=flow then exit;
end;
end;
dec(vd[d[u]]);
if vd[d[u]]=0 then d[vs]:=num;
inc(d[u]);
inc(vd[d[u]]);
end;
procedure push;
var
h,t:longint;
d:array[1..151] of longint;
begin
fillchar(f,sizeof(f),false);
h:=0; t:=1; d[t]:=vs; f[vs]:=true;
repeat
inc(h);
x:=d[h];
for i:=vs to vt do
if (g[x,i]>0) and not f[i] then begin
f[i]:=true;
inc(t);
d[t]:=i;
end;
until h=t;
end;
procedure dfs;
var
i,j,ans:longint;
vv,bf:array[0..151] of boolean;
begin
for i:=1 to n do if not v[i] then break;
for j:=i+1 to n do if not v[j] then break;
if v[j] or (j>n) then exit;
vv:=v;
fillchar(vd,sizeof(vd),0);
fillchar(d,sizeof(d),0);
vd[0]:=num;
g:=bg;
g[vs,i]:=maxlongint; g[j,vt]:=maxlongint;
ans:=0;
while d[vs]<num do ans:=ans+dfs(vs,maxlongint);
push;
for i:=1 to n do
if f[i] then
for j:=1 to n do
if not f[j] then
if cnt[i,j]>ans then cnt[i,j]:=ans;
for i:=1 to n do
if not f[i] then
for j:=1 to n do
if f[j] then
if cnt[i,j]>ans then cnt[i,j]:=ans;
bf:=f;
for i:=1 to n do
if bf[i] then v[i]:=true;
dfs;
v:=vv;
for i:=1 to n do
if not bf[i] then v[i]:=true;
dfs;
end;
begin
readln(t);
for t:=1 to t do begin
readln(n,m);
vs:=0; vt:=n+1; num:=vt+1;
fillchar(bg,sizeof(bg),0);
for i:=1 to m do begin
readln(x,y,c);
inc(bg[x,y],c);
inc(bg[y,x],c);
end;
fillchar(v,sizeof(v),false);
fillchar(cnt,sizeof(cnt),$4f);
dfs;
readln(q);
for i:=1 to q do read(query[i]);
fillchar(ans,sizeof(ans),0);
for i:=1 to q do
for j:=1 to n do
for k:=j+1 to n do
if cnt[j,k]<=query[i] then inc(ans[i]);
for i:=1 to q do writeln(ans[i]);
writeln;
end;
end.
Description
定义无向图中的一条边的值为:这条边连接的两个点的值的异或值。
定义一个无向图的值为:这个无向图所有边的值的和。
给你一个有n个结点m条边的无向图。其中的一些点的值是给定的,而其余的点的值由你决定(但要求均为非负数),使得这个无向图的值最小。在无向图的值最小的前提下,使得无向图中所有点的值的和最小。
Analysis
这题在Amber的论文里出现过。
对于XOR操作,因为每一位都是互相不影响的,所以可以每一位分开来统计。
这样子我们就将点权的取值变成了{0,1}。
然后发现,对于一条边,如果它两端节点的值是不同的,那么这条边的权值为1,否则为0,那么,对于一个确定为1的节点,我们连一条边(S,u,+oo),对于一个确定为0的节点,我们连边(u,T,+oo),对于在原图中出现的边(u,v),替换为两条有向边(u,v,1),(v,u,1)。这样子求出的最小割大小就是原图在这一位的最小值。
我们可以这样理解:与S集联通的点表明该点取1,与T集联通的点表明该点取0。容量为+oo表明该边一定不能被选(因为权值已固定)。而对于原图中存在的边,若两点取值不同,则该边权值为1,在构造出来的图中也就是处于两个不同的集合,而容量为1表明将这两点割开的代价。所以最小割等同于最小代价。
第一问完美解决,第二问的话按我的理解没有那么麻烦(我不知道为什么代码长度都那么长XD)。第二问的意思就是让S集的大小尽量小。而按照上面的构图只需要在残留网络中求出S集的大小即可,这样保证是最小的。
Trick:答案要用int64,当时贡献两个WA我还以为我第二问思路错了....
Code:
var
i,vs,vt,num,n,m,edge,wei:longint;
flag,ans,tmp,cnt1,cnt2:int64;
e,ee:array[1..5000,1..3] of longint;
fr,d,vd,a:array[0..510] of longint;
vis:array[0..510] of boolean;
procedure add(x,y,c:longint);
begin
inc(edge); e[edge,1]:=y; e[edge,2]:=fr[x]; e[edge,3]:=c; fr[x]:=edge;
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
function dfs(u,flow:longint):longint;
var
k,tmp:longint;
begin
if u=vt then exit(flow);
dfs:=0;
k:=fr[u];
while k<>0 do begin
if (e[k,3]>0) and (d[u]=d[e[k,1]]+1) then begin
tmp:=dfs(e[k,1],min(e[k,3],flow-dfs));
inc(dfs,tmp);
dec(e[k,3],tmp);
if odd(k) then inc(e[k+1,3],tmp) else inc(e[k-1,3],tmp);
if dfs=flow then exit;
end;
k:=e[k,2];
end;
dec(vd[d[u]]);
if vd[d[u]]=0 then d[vs]:=num;
inc(d[u]);
inc(vd[d[u]]);
end;
procedure dfs(x:longint);
var
k:longint;
begin
vis[x]:=true;
inc(tmp);
k:=fr[x];
while k<>0 do begin
if (e[k,3]>0) and not vis[e[k,1]] then dfs(e[k,1]);
k:=e[k,2];
end;
end;
begin
readln(n,m);
for i:=1 to n do read(a[i]);
for i:=1 to m do begin
readln(ee[i,1],ee[i,2]);
end;
vs:=0; vt:=n+1; num:=vt+1;
for wei:=0 to 30 do begin
edge:=0;
flag:=1 shl wei;
fillchar(fr,sizeof(fr),0);
fillchar(d,sizeof(d),0);
fillchar(vd,sizeof(vd),0);
for i:=1 to n do
if a[i]>=0 then begin
if a[i] and flag<>0 then begin
add(vs,i,maxlongint);
add(i,vs,0);
end
else begin
add(i,vt,maxlongint);
add(vt,i,0);
end;
end;
for i:=1 to m do begin add(ee[i,1],ee[i,2],1); add(ee[i,2],ee[i,1],1); end;
vd[0]:=num;
ans:=0;
while d[vs]<num do ans:=ans+dfs(vs,maxlongint);
cnt1:=cnt1+ans*flag;
fillchar(vis,sizeof(vis),false);
tmp:=-1;
dfs(vs);
cnt2:=cnt2+tmp*flag;
end;
writeln(cnt1);
writeln(cnt2);
end.
[BZOJ 2127]happiness
这题是我们的学长出的23333...那一届太厉害了而我们这届太水了....
Description
高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。
Analysis
这题的模型是最大权闭合图。
这类题的常规思路就是使失去的代价最小。
考虑构图:S集代表选文科,T集代表选理科。对于一个节点,如果在S集里的话,表明他失去了选择理科的所有愉♂悦值,向T点连边,容量为(该点选理科获得的喜悦值)+(其他同学共同获得的理科喜悦值/2),对于文科同理。
然后对于可以收获共同喜悦值的两个同学,如果他们属于不同的集合,那么他们将会失去所有的共同喜悦值,连双向边,容量为(文科共同喜悦值+理科共同喜悦值)/2。
总喜悦值-这样子求出来的最小割就是答案。
为什么呢?我们考虑只有两个同学的情况。
若两个同学都选文科,那么他们失去的是各自的理科喜悦值和共同理科喜悦值,都是理科同理。如果一理一文的话,这样的构图也是符合要求的。
当然在实际操作的时候,没有必要除以2,只需要把每条边的容量变成两倍即可。
Code:
var
edge,n,m,i,j,x,vs,vt,num,ans,tot:longint;
e:array[1..4000000,1..3] of longint;
fr,d,vd,wen,li,a,b:array[0..10010] of longint;
procedure add(x,y,c:longint);
begin
inc(edge); e[edge,1]:=y; e[edge,2]:=fr[x]; e[edge,3]:=c; fr[x]:=edge;
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
function dfs(u,flow:longint):longint;
var
k,tmp:longint;
begin
if u=vt then exit(flow);
dfs:=0;
k:=fr[u];
while k<>0 do begin
if (e[k,3]>0) and (d[u]=d[e[k,1]]+1) then begin
tmp:=dfs(e[k,1],min(e[k,3],flow-dfs));
inc(dfs,tmp);
dec(e[k,3],tmp);
if odd(k) then inc(e[k+1,3],tmp) else inc(e[k-1,3],tmp);
if dfs=flow then exit;
end;
k:=e[k,2];
end;
dec(vd[d[u]]);
if vd[d[u]]=0 then d[vs]:=num;
inc(d[u]);
inc(vd[d[u]]);
end;
function get(x,y:longint):longint;
begin
exit((x-1)*m+y);
end;
begin
readln(n,m);
for i:=1 to n do
for j:=1 to m do begin
read(wen[get(i,j)]);
tot:=tot+wen[get(i,j)];
wen[get(i,j)]:=wen[get(i,j)]*2;
end;
for i:=1 to n do
for j:=1 to m do begin
read(li[get(i,j)]);
tot:=tot+li[get(i,j)];
li[get(i,j)]:=li[get(i,j)]*2;
end;
for i:=1 to n-1 do
for j:=1 to m do begin
read(x);
tot:=tot+x;
a[get(i,j)]:=x;
wen[get(i,j)]:=wen[get(i,j)]+x;
wen[get(i+1,j)]:=wen[get(i+1,j)]+x;
end;
for i:=1 to n-1 do
for j:=1 to m do begin
read(x);
tot:=tot+x;
a[get(i,j)]:=a[get(i,j)]+x;
li[get(i,j)]:=li[get(i,j)]+x;
li[get(i+1,j)]:=li[get(i+1,j)]+x;
end;
for i:=1 to n do
for j:=1 to m-1 do begin
read(x);
tot:=tot+x;
b[get(i,j)]:=x;
wen[get(i,j)]:=wen[get(i,j)]+x;
wen[get(i,j+1)]:=wen[get(i,j+1)]+x;
end;
for i:=1 to n do
for j:=1 to m-1 do begin
read(x);
tot:=tot+x;
b[get(i,j)]:=b[get(i,j)]+x;
li[get(i,j)]:=li[get(i,j)]+x;
li[get(i,j+1)]:=li[get(i,j+1)]+x;
end;
vs:=0; vt:=n*m+1; num:=vt+1; vd[0]:=num;
for i:=1 to n do
for j:=1 to m do begin
add(vs,get(i,j),wen[get(i,j)]);
add(get(i,j),vs,0);
add(get(i,j),vt,li[get(i,j)]);
add(vt,get(i,j),0);
end;
for i:=1 to n-1 do
for j:=1 to m do begin
add(get(i,j),get(i+1,j),a[get(i,j)]);
add(get(i+1,j),get(i,j),a[get(i,j)]);
end;
for i:=1 to n do
for j:=1 to m-1 do begin
add(get(i,j),get(i,j+1),b[get(i,j)]);
add(get(i,j+1),get(i,j),b[get(i,j)]);
end;
while d[vs]<num do ans:=ans+dfs(vs,maxlongint);
writeln(tot-(ans shr 1));
end.