题解 P3329 【[ZJOI2011]最小割】

· · 题解

广告

蒟蒻的blog

正文

首先我们明确一点:这道题不是让你把n^2个最小割跑一遍【废话】

但是最小割过程是必要的,因为最小割并没有别的效率更高的算法(Stoer-Wagner之类的?)

那我们就要尽量找办法减少做最大流(求最小割)的次数

最小割树

就像最小生成树一样,最小割也有自己的生成树

我们新建立一个有n个点,没有边的无向图

我们在原无向图中任选两个点S,T,求出S-T最小割,那么可以在S-T中间加一条权值等于最小割值得无向边

然后,分别对S属于的点集合和T属于的点集合递归做上面的过程,直到当前处理的集合只剩下一个点了

现在,对于这棵新树(显然是一棵树,可以自己退一下为什么),有一个结论:

树上任意两个点在原图中的对应点之间的最小割值等于这两个点的树上路径中边权的最小值

证明?我也不知道啊!

但是这个算法的正确性是可以保证的(你也可以感性理解一下qwq)

做法

有了这个“大杀器”以后,这道题也就迎刃而解了~

这道题目的点数很小,因此我们只要把所有搞出来的最小割值依此更新点对

处理询问的时候就把所有的点对扫一遍输出就好了

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#define inf 1e9
using namespace std;
inline int read(){
    int re=0,flag=1;
    scanf("%d",&re);return re;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,m,cnt,first[210],dep[210],cur[210],ans[160][160];
struct edge{
    int to,next,w,W;
}a[10010];
inline void add(int u,int v,int w){
    a[++cnt]=(edge){v,first[u],w,w};first[u]=cnt;
    a[++cnt]=(edge){u,first[v],w,w};first[v]=cnt;
}
bool bfs(int s,int t){
    int q[210],head=0,tail=1,i,u,v;
    for(i=1;i<=n;i++) dep[i]=-1,cur[i]=first[i];
    q[0]=s;dep[s]=0;
    while(head<tail){
        u=q[head++];
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;
            if(~dep[v]||!a[i].w) continue;
            dep[v]=dep[u]+1;q[tail++]=v;
        }
    }
    return ~dep[t];
}
int dfs(int u,int t,int limit){
    if(u==t||!limit) return limit;
    int i,v,f,flow=0;
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;
        if(dep[v]==dep[u]+1&&(f=dfs(v,t,min(limit,a[i].w)))){
            a[i].w-=f;a[i^1].w+=f;
            flow+=f;limit-=f;
            if(!limit) return flow;
        }
    }
    return flow;
}
int dinic(int s,int t){
    int re=0;
    while(bfs(s,t)) re+=dfs(s,t,inf);
    return re;
}
void clear(){
    for(int i=0;i<=cnt;i++) a[i].w=a[i].W;
}
void init(){
    memset(first,-1,sizeof(first));memset(a,0,sizeof(a));cnt=-1;
    memset(ans,127,sizeof(ans));
}
int node[210],vis[210];
void cut(int u){
    int i,v;vis[u]=1;
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;
        if(a[i].w&&!vis[v]) cut(v);
    }
}
void solve(int l,int r){
    if(l==r) return;
    clear();int i,j,tmp,t[2][210]={0};
    memset(vis,0,sizeof(vis));
    tmp=dinic(node[l],node[r]);
    cut(node[l]);
    for(i=1;i<=n;i++)
        if(vis[i])
            for(j=1;j<=n;j++)
                if(!vis[j])
                    ans[i][j]=ans[j][i]=min(ans[i][j],tmp);
    for(i=l;i<=r;i++) t[vis[node[i]]][++t[vis[node[i]]][0]]=node[i];
    for(i=l,j=1;j<=t[0][0];j++,i++) node[i]=t[0][j];
    for(i=l+t[0][0],j=1;j<=t[1][0];j++,i++) node[i]=t[1][j];
    solve(l,l+t[0][0]-1);solve(l+t[0][0],r);
}
int main(){
    int i,j,k,T=read(),t1,t2,t3,q;
    while(T--){
        init();
        n=read();m=read();
        for(i=1;i<=m;i++) t1=read(),t2=read(),t3=read(),add(t1,t2,t3);
        for(i=1;i<=n;i++) node[i]=i;
        solve(1,n);
        q=read();
        for(k=1;k<=q;k++){
            t1=read();int tmp=0;
            for(i=1;i<=n;i++){
                for(j=i+1;j<=n;j++) if(ans[i][j]<=t1) tmp++;
            }
            printf("%d\n",tmp);
        }
        puts("");
    }
}