题解 P4897 【【模板】最小割树(Gomory-Hu Tree)】

· · 题解

终于AC这一题了,发个题解庆祝一下吧!!!

首先,大家要先用Dinic或ISAP做对【模板】网络最大流(不会网络最大流的可以点这里),根据推断,网络最大流=网络最小割。

首先,我们要知道。按照正常暴力的算法,这一题会被T飞的。为了防止TLE事件再发生。我们就要采取相应的优化措施。

怎么弄?其实也不难。我们可以建立一棵最小割树。 (最小割树?啥?)

最小割树=分治+最小割(最大流)

其实,我们可以这样做:

先建立一个无向图,然后再把这个图的整体看做一个集合,然后任意选择两个点(设它们为1和5),在原图上跑一趟最小割。如图:

然后,因为1和5已经无法连接了,所以我们可以把1和5再分成两个集合,中间由一条边连接,这条边的权值为两点之间的最小割。然后,我们再对每个集合进行这样的操作(注意,最小割要在原图上跑),再分下去,直到集合内只剩一个节点为止。如图:

最后,我们可以得到一棵最小割树。

这里要注意,跑最小割(最大流)要在原图上跑!!!

大功告成(〃'▽'〃),这棵树中任意两点的最小割等于原图上任意两点的最小割,因此,我们直接用BFS来查找就行了,查找一个的时间复杂度是O(n),这样求Q个最小割只需要用O(Qn)的时间复杂度,查询代码如下:

int findans()
{
    head=0,tail=1;
    d[head]=a;
    for(register int i=0;i<=n;i++) dis[i]=0;
    dis[a]=0x7ffffff;
    while(head<tail)
    {
        t=d[head];
        head++;
        for(register int i=trf[t];i;i=tr[i].next)
        {
            if(dis[trar]==0)
            {
                dis[trar]=min(dis[t],tr[i].flow+1);
                if(trar==b) return dis[b]-1;
                d[tail]=trar;
                tail++;
            }
        }
    }
    return dis[b]-1;
}

然后就是如何构建这一棵树。其实也不难,因为Dinic或ISAP跑dfs时图中有一些边的容量为0,其实就代表要割掉那一条边,所以我们只需要用搜索的方式来寻找两个集合中分别包含那些数,代码如下:

void build(int l,int r)
{
    if(l>=r)return;
    //在原图上跑两点之间的最小割
    a=v[l];
    b=v[l+1];
    dinic();
    //给两个集合之间加一条无向边
    adtr(a,b,ans);
    adtr(b,a,ans);
    //寻找两个集合的元素
    int tl1=0,tl2=0;
    for(register int i=l;i<=r;i++)
    {
        if(dis[v[i]])
        {
            tl1++;
            t1[tl1]=v[i];
        }
        else
        {
            tl2++;
            t2[tl2]=v[i];
        }
    }
    for(register int i=1;i<=tl1;i++) v[i+l-1]=t1[i];
    for(register int i=1;i<=tl2;i++) v[l+tl1+i-1]=t2[i];
    //分治下去
    build(l,tl1+l-1);
    build(l+tl1,r);
}

好了,最后来一份完整代码:

#pragma GCC optimize(3)
#include<cstdio>
#define N 100010
#define kar k[i].ar
#define trar tr[i].ar
#define min(a,b) (a<b?a:b)
using namespace std;
int a,b,t,n,m,f[N],ans,trl;
struct node {
    int next,ar,flow;
} k[N*20],tr[N*20];
int trf[N],ss;
int first[N],dis[N],len;
int x[N],y[N],v[N],e[N];
int t1[N],t2[N],tl1,tl2;
void add(int a,int b,int t) {
    len++;
    k[len].ar=b;
    k[len].next=first[a];
    first[a]=len;
    k[len].flow=t;
}
void adtr(int a,int b,int t) {
    trl++;
    tr[trl].ar=b;
    tr[trl].next=trf[a];
    trf[a]=trl;
    tr[trl].flow=t;
}
int head,tail,d[N];
bool bfs() {
    head=0,tail=1;
    d[0]=a;
    for(register int i=0; i<=n; i++) dis[i]=0;
    dis[a]=1;
    while(head<tail) {
        t=d[head];
        head++;
        for(register int i=first[t]; i; i=k[i].next) {
            if(dis[kar]==0&&k[i].flow>0) {
                dis[kar]=dis[t]+1;
                if(kar==b) return true;
                d[tail]=kar;
                tail++;
            }
        }
    }
    return false;
}
int dfs(int xx,int flow) {
    if(xx==b) return flow;
    if(flow==0) return 0;
    if(dis[xx]>=dis[b])return 0;
    int h,s=0;
    for(register int i=first[xx]; i>1; i=k[i].next) {
        if(flow==0) {
            dis[xx]=0;
            break;
        }
        if(dis[kar]==dis[xx]+1&&k[i].flow>0) {
            h=dfs(kar,min(flow,k[i].flow));
            s+=h;
            flow-=h;
            k[i].flow-=h;
            k[i^1].flow+=h;
            if(h==0) dis[kar]=0;
        }
    }
    return s;
}
void dinic() {
    for(register int i=0; i<=n; i++) {
        dis[i]=0;
        first[i]=0;
    }
    len=1,ans=0;
    for(register int i=1; i<=m; i++)
        add(x[i],y[i],f[i]),add(y[i],x[i],f[i]);
    while(bfs()) ans+=dfs(a,0x7ffffff);
}
void build(int l,int r) {
    if(l>=r)return;
    a=v[l];
    b=v[l+1];
    dinic();
    adtr(a,b,ans);
    adtr(b,a,ans);
    int tl1=0,tl2=0;
    for(register int i=l; i<=r; i++) {
        if(dis[v[i]]) tl1++,t1[tl1]=v[i];
        else tl2++,t2[tl2]=v[i];
    }
    for(register int i=1; i<=tl1; i++) v[i+l-1]=t1[i];
    for(register int i=1; i<=tl2; i++) v[l+tl1+i-1]=t2[i];
    build(l,tl1+l-1);
    build(l+tl1,r);
}
int findans() {
    head=0,tail=1;
    d[head]=a;
    for(register int i=0; i<=n; i++) dis[i]=0;
    dis[a]=0x7ffffff;
    while(head<tail) {
        t=d[head];
        head++;
        for(register int i=trf[t]; i; i=tr[i].next) {
            if(dis[trar]==0) {
                dis[trar]=min(dis[t],tr[i].flow+1);
                if(trar==b) return dis[b]-1;
                d[tail]=trar;
                tail++;
            }
        }
    }
    return dis[b]-1;
}
int main() {
    int T;
    len=1;
    scanf("%d%d",&n,&m);
    for(register int i=1; i<=m; i++) {
        scanf("%d%d%d",&a,&b,&t);
        x[i]=a,y[i]=b,f[i]=t;
    }
    for(register int i=0; i<=n; i++) v[i]=i;
    build(0,n);
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&a,&b);
        printf("%d\n",findans());
    }
    return 0;
}

好了,到此结束了(逃)。