天天看点

最小割树详解[模板]

问题

n

m

,

q

,

,

给定n个点m条边的无向图,q个询问,每次询问两个点,求最小割

给定n个点m条边的无向图,q个询问,每次询问两个点,求最小割

q

<

=

1

e

5

\color{Red}q<=1e5

q<=1e5

,

=

=

两点的最小割跑一边最大流就好了,因为最大流==最小割

两点的最小割跑一边最大流就好了,因为最大流==最小割

但是面对这么多的询问,就需要我们的最小割树登场了。

具体做法

,

首先最小割树是一颗树,有着这样一个性质

首先最小割树是一颗树,有着这样一个性质

s

t

,

s

t

s

t

\color{Red}对于树中任意两点s和t,s和t在原图的最小割等于最小割树上s到t路径上的最小边

对于树中任意两点s和t,s和t在原图的最小割等于最小割树上s到t路径上的最小边

构造完成之后,就可以树上倍增求两点间最小边权(就是lca的树上倍增)

考虑如何来构造

.

,

,

,

Ⅰ.选择图中任意两点,求出最小割,在最小割树中连接这两点,边权为最小割

Ⅰ.选择图中任意两点,求出最小割,在最小割树中连接这两点,边权为最小割

.

,

,

Ⅱ.这两点的最小割把图划分为两个集合,在两个集合中继续分治,重复步骤Ⅰ

Ⅱ.这两点的最小割把图划分为两个集合,在两个集合中继续分治,重复步骤Ⅰ

这样分集合最后会分到集合中只有1个点,算法结束,最小割树构造完成

,

\color{Red}至于最小割树为什么有这样的定理,我只有自己的感性理解

至于最小割树为什么有这样的定理,我只有自己的感性理解

,

,

所有读者感兴趣自行去了解吧,我比较菜,记住就好了。

所有读者感兴趣自行去了解吧,我比较菜,记住就好了。

#include <bits/stdc++.h>
using namespace std;
const int inf=1e9;
const int maxn=1e5+10;
struct edge{
  int to,nxt,flow;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int flow){
  d[++cnt]=(edge){ v,head[u],flow },head[u]=cnt;
  d[++cnt]=(edge){ u,head[v],0 },head[v]=cnt;
}
int dis[maxn],n,m;
bool bfs(int s,int t)//普通的最大流找增广路 
{
  for(int i=0;i<=n+1;i++) dis[i]=0;
  dis[s]=1;
  queue<int>q; q.push( s );
  while( !q.empty() )
  {
    int u=q.front(); q.pop();
    for(int i=head[u];i;i=d[i].nxt )
    {
      int v=d[i].to;
      if( dis[v]==0&&d[i].flow )
      {
        dis[v]=dis[u]+1;
        if( v==t )  return true;
        q.push( v );
      }
    }
  }
  return false;
}
int dinic(int u,int t,int flow )//普通的最大流 
{
  if( u==t )  return flow;
  int res=flow;
  for(int i=head[u];i;i=d[i].nxt )
  {
    int v=d[i].to;
    if( dis[v]==dis[u]+1&&d[i].flow )
    {
      int temp=dinic(v,t,min(res,d[i].flow) );
      res-=temp;
      if( temp==0 ) dis[v]=0;
      d[i].flow-=temp;
      d[i^1].flow+=temp;
    }
    if( res==0 )  break;
  }
  return flow-res;
}
void init()
{
  for(int i=2;i<=cnt;i+=2)//撤流操作,因为上一次跑最大流改变了图的流量,改回来 
    d[i].flow=( d[i].flow+d[i^1].flow ),d[i^1].flow=0;
}
int maxflow(int s,int t)
{
  init();//退流
  int ans=0;
  while( bfs(s,t) ) ans+=dinic(s,t,inf);
  return ans; 
}
class GHT
{
  public:
    edge d[maxn];
    int head[maxn],cnt;
    int temp1[maxn],temp2[maxn],node[maxn];
    void add(int u,int v,int flow){
      d[++cnt]=(edge){ v,head[u],flow },head[u]=cnt;
      d[++cnt]=(edge){ u,head[v],flow},head[v]=cnt;
    }
    void build(int l,int r)
    {
      if( l>=r )  return;
      int x=node[l],y=node[l+1];
      int cut = maxflow(x,y);
      add(x,y,cut );
      int top1=0,top2=0;
      //去掉x和y,分成集合temp1和temp2 
      for(int i=l;i<=r;i++)
      {
        //最后一次bfs后有深度,说明和x相连接,放在左集合temp1 
        if( dis[ node[i] ] )  temp1[++top1]=node[i];
        else  temp2[++top2]=node[i]; 
      }
      for(int i=l;i<=l+top1-1;i++)  node[i]=temp1[i-l+1];
      for(int i=l+top1;i<=r;i++)  node[i]=temp2[i-top1-l+1];
      build(l,l+top1-1); build(l+top1,r);//两个集合分别建图 
    }
    int deep[maxn],fa[maxn][23],ans[maxn][23],log2n;
    void dfs(int x,int father)
    {
      fa[x][0]=father;
      deep[x]=deep[father]+1;
      for(int i=1;i<=log2n;i++)
      {
        fa[x][i]=fa[ fa[x][i-1] ][i-1];
        ans[x][i]=min( ans[x][i-1],ans[ fa[x][i-1] ][i-1]);
      }
      for(int i=head[x];i;i=d[i].nxt )
      {
        int v=d[i].to;
        if( v!=father )
        {
          ans[v][0]=d[i].flow;
          dfs(v,x);
        } 
      }
    }
    int query(int x,int y)
    {
      int zhi=inf;
      if( deep[x]<deep[y] ) swap(x,y);
      for(int i=log2n;i>=0;i-- )
        if( deep[ fa[x][i] ]>=deep[y] )
        {
          zhi=min(zhi,ans[x][i] );
          x=fa[x][i];
        }
      if( x==y )  return zhi;
      for(int i=log2n;i>=0;i-- )
      {
        if( fa[x][i]!=fa[y][i] )
        {
          zhi=min( zhi,min( ans[x][i],ans[y][i] ) );
          x=fa[x][i],y=fa[y][i];
        }
      }
      zhi=min(zhi,min( ans[x][0],ans[y][0] ) );
      return zhi;
    }
}CUT;
void CUT_init()
{
  CUT.log2n=log2(n)+1;
  CUT.cnt=1;//是链式前向星的边编号 
  for(int i=1;i<=n;i++) CUT.node[i]=i;//这是储存节点集合的信息 
  CUT.build(1,n);//建树 
  CUT.dfs(1,0);//倍增 
}
int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++)
  {
    int l,r,flow; scanf("%d%d%d",&l,&r,&flow);
    add( l,r,flow );  add(r,l,flow);//无向图网络流建边 
  }
  CUT_init();//初始化最小割树 
  int q; scanf("%d",&q);
  for(int i=1;i<=q;i++)
  {
    int s,t; scanf("%d%d",&s,&t);
    int _= CUT.query(s,t);
    if( _==inf )  _=-1;
    printf("%d\n",_);
  }
}