[模板]最小割樹(Gomory-Hu Tree)(luogu4897)
阿新 • • 發佈:2018-12-13
給定一個\(n\)個點\(m\)條邊的無向連通圖,多次詢問兩點之間的最小割
兩點間的最小割是這樣定義的:原圖的每條邊有一個割斷它的代價,你需要用最小的代價使得這兩個點不連通
Input
第一行兩個數\(n,m\)
接下來\(m\)行,每行3個數\(u,v,w\),表示有一條連線\(u\)與\(v\)的無向邊,割斷它的代價為\(w\)
接下來這一行有一個整數\(Q\),表示詢問次數
接下來\(Q\)行,每行兩個數\(u,v\),你需要求出\(u\)與\(v\)之間的最小割
Output
輸出共\(Q\)行,每行一個整數對應詢問的答案
Sample Input
4 5 1 2 2 2 3 2 4 2 3 4 3 1 1 3 1 3 1 4 2 4 2 3
Sample Output
3
4
4
Hint
\(n\leq 500,\quad m\leq 1500,\quad Q\leq 10^5,\quad 0\leq w\leq 10^4\)
題意:
求任意兩點間的最小割(最大流)
題解:
本題要用到最小割樹。
最小割樹其實就是把所有的點分成多個部分然後分治,使只用跑很少次網路流就能解決兩點之間的最小割。
舉個例子:
這個圖:
開始先求1,4點間的最小割,易得為3。
跑完網路流之後的圖是這樣的。
我們發現圖變成了兩部分,事實上,圖肯定會變成兩部分甚至更多,因為既然是一個割,就肯定會把兩個點分到不同的部分。
然後易知兩個區域之間的最小割至少為當前的最小割——3。
當前\(ans\)
\[ \begin{matrix} 0 & 3 & 3 & 3 \\ 3 & 0 & inf & inf \\ 3 & inf & 0 & inf \\ 3 & inf & inf & 0 \end{matrix} \]
然後我們把圖復原
在剛才劃分的區域裡繼續劃分
但有一個區間只剩一個點了,所以不繼續劃分,取(2,3,4)中的2,4兩點做最小割,易得為4。
剩下的圖為:
然後易知兩個區域之間的最小割至少為當前的最小割——4。
然後更新答案,機住,就算不在當前區間內的數也必須更新。
當前\(ans\)
\[ \begin{matrix} 0 & 3 & 3 & 3 \\ 3 & 0 & 4 & inf \\ 3 & 4 & 0 & 4 \\ 3 & inf & 4 & 0 \end{matrix} \]
繼續復原,更新,然後得到最後的\(ans\):
\[ \begin{matrix} 0 & 3 & 3 & 3 \\ 3 & 0 & 4 & 4 \\ 3 & 4 & 0 & 4 \\ 3 & 4 & 4 & 0 \end{matrix} \]
然後就可以根據詢問輸出了。
#include<bits/stdc++.h>
#define re register
using namespace std;
const int inf=1<<29,N=1010,M=20010;
int n,m,a[N];
int ans[N][N];
int head[N],nxt[M],bian[M],zhi[M],tot;
void init(){
tot=1;
memset(head,0,sizeof head);
}
inline void add(re int x,re int y,re int z){
tot++;bian[tot]=y;zhi[tot]=z;nxt[tot]=head[x];head[x]=tot;
tot++;bian[tot]=x;zhi[tot]=z;nxt[tot]=head[y];head[y]=tot;
}
inline void build(int m){
for(re int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
}
void rebuild(){
for(re int i=1;i<=tot;i+=2){
zhi[i]=zhi[i^1]=(zhi[i]+zhi[i^1])>>1;
}
}
int v[N],d[N];
void cut(int x){
v[x]=1;
for(int i=head[x];i;i=nxt[i]){
if(zhi[i]&&!v[bian[i]])cut(bian[i]);
}
}
queue<int>q;
bool bfs(int b,int e){
memset(d,0,sizeof(d));
while(!q.empty())q.pop();
q.push(b);d[b]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(zhi[i] && !d[bian[i]]){
q.push(bian[i]);
d[bian[i]]=d[x]+1;
if(bian[i]==e)return 1;
}
}
}
return 0;
}
int dinic(int b,int e,int x,int flow){
if(x==e)return flow;
int rest=flow,k;
for(int i=head[x];i && rest;i=nxt[i]){
if(zhi[i] && d[bian[i]]==d[x]+1){
k=dinic(b,e,bian[i],min(rest,zhi[i]));
if(!k)d[bian[i]]=0;
zhi[i]-=k;
zhi[i^1]+=k;
rest-=k;
}
}
return flow-rest;
}
inline int maxflow(int b,int e){
int flow=0,maxflow=0;
while(bfs(b,e)){
while(flow=dinic(b,e,b,inf))maxflow+=flow;
}
return maxflow;
}
int b,e;
void solve(int l,int r){
if(l==r)return;
rebuild();
b=a[l],e=a[r];
re int mincut=maxflow(b,e);
memset(v,0,sizeof v);
cut(b);
for(re int i=1;i<=n;++i){
if(!v[i])continue;
for(re int j=1;j<=n;++j){
if(v[j])continue;
ans[i][j]=ans[j][i]=min(ans[i][j],mincut);
}
}
re int cnt=l-1;
static int ls[N];
for(re int i=l;i<=r;++i){
if(v[a[i]]){
ls[++cnt]=a[i];
}
}
re int fj=cnt;
for(re int i=l;i<=r;++i){
if(!v[a[i]]){
ls[++cnt]=a[i];
}
}
for(re int i=l;i<=r;++i)a[i]=ls[i];
solve(l,fj);
solve(fj+1,r);
}
int main()
{
int b,e,q;
memset(ans,0x3f,sizeof ans);
cin>>n>>m;
init();
build(m);
for(int i=1;i<=n;++i){
a[i]=i;
}
solve(1,n);
cin>>q;
while(q--){
scanf("%d%d",&b,&e);
if(ans[b][e]==0x3f3f3f3f)ans[b][e]=2147483647;
printf("%d\n",ans[b][e]);
}
}