P4897 【模板】最小割树(Gomory-Hu Tree)
题意:
分析:
- 前置芝士:最小割
简单证明几个引理 :
- 我们记 \(\delta(x,y)\) 表示 \((x,y)\) 之间的最小割代价, \(\forall \ p\in V_x,\ q\in V_y,\delta(x,y)\ge \delta(p,q)\)
证明: 反证,若 \(\delta(p,q)<\delta(x,y)\) 那么割断 \(p,q\) 的代价不足以割断 \(x,y\) 而 \(p,q\) 分别和 \(x,y\) 相连
- 任意三个点 \(a,b,c\) 存在 \(\delta(a,b)\ge min(\delta(a,b),\delta(a,c),\delta(b,c))\)
证明: 我们令 $\delta (a,b) $ 为最小的一对,那么割掉 \(a\to b\) 之后我们假设 \(c\) 与 \(b\) 相连 ,那么由上面的引理可得 \(\delta(a,c)\le \delta(a,b)\) 又因为 \(\delta(a,b)\le \delta(a,c)\) 那么 \(\delta(a,b)=\delta(a,c)\)
-
对于任意不同的两点u,v, \(\delta(u,v) \geq min(\delta(u,w_1),\delta(w_1,w_2),\delta(w_2,w_3) \dots , \delta(w_k,v))\)
-
对于任意不同的两点 \(u,v\) ,令 \(p,q\) 为最小割树x到y路径上的两点,且 \(\delta(p,q)\) 最小,那么 \(\delta(u,v)=\delta(p,q)\) .也就是说,u,v 两点最小割就是最小割树上u到v的路径上权值最小的边
证明: 由 \(1\) 得 \(\delta(u,v)\ge \delta(p,q)\) 又因为 \(\delta(p,q)\le \delta(u,v)\) 那么 \(\delta(u,v)=\delta(p,q)\)
好了,有了上面里的定理,我们就可以开始构造最小割树了,我们每次找到两个点,求他们的最小割,然后按照与这两个点的联通性划分成两个点集,递归处理 , 这样每次跑了 \(n\) 次最大流,每一次的复杂度 \(n^2m\) 总体复杂度 \(O(n^3m)\) ,\(1e11\) 呀真就过了/EE
tip:
- 虽然每一次划分成了两个点集,但是求最小割还是用的是全局的图,而且我们每次得还原之前更新的流量
- 同上,虽然固定了源汇,但是所有的点的信息都需要更新(因为有的点的 \(dep\) 没有更新,我调了半个多小时)
代码:
#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
using namespace std;
namespace zzc
{
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 505;
const int maxm = 1505;
const int inf = 0x3f3f3f3f;
int n,m,qt;
namespace network
{
int cnt=1,st,ed;
int head[maxn],dep[maxn],cur[maxn];
struct edge
{
int to,nxt,w;
}e[maxm<<1];
queue<int> q;
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void add_edge(int u,int v,int w)
{
add(u,v,w);add(v,u,w);
}
bool bfs()
{
for(int i=0;i<=n;i++) dep[i]=-1,cur[i]=head[i];
q.push(st);dep[st]=0;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]==-1&&e[i].w)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
return dep[ed]!=-1;
}
int dfs(int u,int flow)
{
if(u==ed) return flow;
int used=0,w;
for(int &i=cur[u];i;i=e[i].nxt)//当前弧优化
{
int v=e[i].to;
if(dep[v]==dep[u]+1&&e[i].w)
{
w=dfs(v,min(e[i].w,flow-used));
e[i].w-=w;
e[i^1].w+=w;
used+=w;
if(used==flow) return used;
}
}
if(!used) dep[u]=-1;
return used;
}
void init(int s,int t)
{
st=s;ed=t;
for(int i=2;i<=cnt;i+=2)
{
e[i].w=(e[i].w+e[i^1].w)/2;
e[i^1].w=e[i].w;
}
}
int dinic(int s,int t)
{
init(s,t);
int res=0,fl;
while(bfs())
{
fl=dfs(st,inf);
res+=fl;
}
return res;
}
}
namespace mincut_tree
{
int cnt;
int head[maxn],tmp1[maxn],tmp2[maxn],pos[maxn],ans[maxn][maxn];
struct edge
{
int to,nxt,w;
}e[maxm<<1];
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].w=w;
head[u]=cnt;
}
void add_edge(int u,int v,int w)
{
add(u,v,w);add(v,u,w);
}
void build(int l,int r)
{
if(l==r) return ;
int cnt1=0,cnt2=0;
add_edge(pos[l],pos[l+1],network::dinic(pos[l],pos[l+1]));
for(int i=l;i<=r;i++) if(network::dep[pos[i]]!=-1) tmp1[++cnt1]=pos[i];else tmp2[++cnt2]=pos[i];
for(int i=l;i<=l+cnt1-1;i++) pos[i]=tmp1[i-l+1];
for(int i=l+cnt1;i<=r;i++) pos[i]=tmp2[i-cnt1-l+1];
build(l,l+cnt1-1);
build(l+cnt1,r);
}
void dfs(int u,int ff,int id)
{
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==ff) continue;
ans[id][v]=min(ans[id][u],e[i].w);
dfs(v,u,id);
}
}
void work()
{
memset(ans,0x3f,sizeof(ans));
for(int i=0;i<=n;i++) pos[i]=i;
random_shuffle(pos,pos+n+1);
build(0,n);
for(int i=0;i<=n;i++)
{
dfs(i,-1,i);
}
}
}
void work()
{
int a,b,c;
n=read();m=read();
for(int i=1;i<=m;i++)
{
a=read();b=read();c=read();
network::add_edge(a,b,c);
}
mincut_tree::work();
qt=read();
while(qt--)
{
a=read();b=read();
printf("%d\n",mincut_tree::ans[a][b]);
}
}
}
int main()
{
zzc::work();
return 0;
}