Cqoi2016不同的最小割(最小割树,网络流)

4519: [Cqoi2016]不同的最小割

Description

学过图论的同学都知道最小割的概念:对于一个图,某个对图中结点的划分将图中所有结点分成两个部分,如果结点 s s ,t不在同一个部分中,则称这个划分是关于 s s ,t的割。对于带权图来说,将所有顶点处在不同部分的边的权值相加所得到的值定义为这个割的容量,而 s s ,t的最小割指的是在关于 s s ,t的割中容量最小的割。
而对冲刺 NOI N O I 竞赛的选手而言,求带权图中两点的最小割已经不是什么难事了。我们可以把视野放宽,考虑有 N N 个点的无向连通图中所有点对的最小割的容量,共能得到N(N1)/2个数值。
这些数值中互不相同的有多少个呢?这似乎是个有趣的问题。

Input

输入文件第一行包含两个数 N N M,表示点数和边数。接下来 M M 行,每行三个数u v v w
表示点u和点v(从1开始标号)之间有条边权值是 w w
1<=N<=8501<=M<=85001<=W<=100000

Output

输出文件第一行为一个整数,表示个数。

Sample Input
4 4
1 2 3
1 3 6
2 4 5
3 4 4
Sample Output
3













解:

最小割树练习题。
本题解不证明最小割树,要详细证明的可以自行百度。最小割树解决一类问题:无向图任意两点询问最小割,在预处理之后可以转化为树上求瓶颈路。但是由于预处理的巨大复杂度,后面查询的复杂度几乎可以忽略不计。
最小割树:构造一棵树,用树来代表一个无向图,使得任意两点图上最小割等于树上最小割。而树上最小割就是瓶颈路。
如何构造?可以想一想一张图中最小割最多不会超过点数个,具体证明我也不会。首先把所有点放进一个集合里,集合内任选两点在原图中跑最小割,分成s集合和 t t 集合两集合连边,表示这两个集合之间的最小割。然后递归做这个过程。
注意:每次跑最小割的时候要还原整张图。也就是说即使不在集合内点,也要放进图跑。但是每次选源汇一定是同一个集合。

可以发现,每跑一次最小割可以连一条边,总共需要跑n次网络流。复杂度感人。似乎只能做比较板的题。

吐槽一下:果然CQOI的题很板。

code:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<set>
using namespace std;
struct lxt{
    int to,next,flow;
}eg[17005];

int head[855],cnt=-1,n,m,a1,a2,a3,s,t;
int p[855],layer[855],cir[855];
bool vis[855];
set <int> ans;
queue <int> d;

void add(int op,int ed,int flow){
    eg[++cnt].next=head[op];
    eg[cnt].to=ed;
    eg[cnt].flow=flow;
    head[op]=cnt;
}

bool bfs(){
    memset(cir,0,sizeof(cir));
    memset(layer,0,sizeof(layer));
    d.push(s);layer[s]=1;
    while(!d.empty()){
        int now=d.front();d.pop();
        for(int i=head[now];i!=-1;i=eg[i].next)
          if(eg[i].flow!=0&&layer[eg[i].to]==0)
            layer[eg[i].to]=layer[now]+1,d.push(eg[i].to);
    }
    return layer[t];
}

int dfs(int u,int a){
    if(u==t||a==0) return a;
    int f,flow=0;
    if(cir[u]==0) cir[u]=head[u];
    for(int &i=cir[u];i!=-1;i=eg[i].next)
      if(eg[i].flow!=0&&layer[u]+1==layer[eg[i].to]){
        f=dfs(eg[i].to,min(a,eg[i].flow));
        flow+=f;a-=f;
        eg[i].flow-=f;eg[i^1].flow+=f;
        if(a==0) break;
      }
    return flow;
}

int dinic(){
    int ret=0;
    while(bfs()) ret+=dfs(s,0x7f7f7f7f);
    return ret;
}

int findit(int u){
    vis[u]=1;
    for(int i=head[u];i!=-1;i=eg[i].next)
      if(eg[i].flow!=0&&vis[eg[i].to]==0)
        findit(eg[i].to);
}

void sovle(int l,int r){
    if(l==r) return;
    s=p[l];t=p[r];
    ans.insert(dinic());
    memset(vis,0,sizeof(vis));
    findit(s);
    for(int i=1;i<=cnt;i+=2)
      eg[i].flow=(eg[i].flow+eg[i^1].flow)>>1,eg[i^1].flow=eg[i].flow;
    int t1=l,t2=r;
    while(t1<t2){
        if(vis[p[t1]]==1) t1++;
        else if(vis[p[t2]]==0) t2--;
        else swap(p[t1],p[t2]);
    }
    sovle(l,t1-1);sovle(t1,r);
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
      scanf("%d%d%d",&a1,&a2,&a3),add(a1,a2,a3),add(a2,a1,a3);
    for(int i=1;i<=n;i++) p[i]=i;
    sovle(1,n);
    printf("%d",ans.size());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值