bzoj4519[Cqoi2016]不同的最小割

39 篇文章 0 订阅
5 篇文章 0 订阅

bzoj4519[Cqoi2016]不同的最小割

题目在这里呀

这是我第一次遇到最小割树,特地写一个题解来记忆一下啊。(话说12月份好像好久没更博了ww)

最小割树=分治+最小割

此题的要求嘛就是求两两点对之间不同的最小割有几种。

再概括一下就是把n个点分成两部分,最小割就是两个部分的点之间的连边之和,求这个不同的和有几个。

注意到最小割树是针对无向图的。

这里就来讲一下最小割树的实现(这是道裸的最小割树辣)

1、先将所有点放在同一个集合中,任取两个点(方便起见用1和n),跑一遍最小割

2、这样就把整个集合分成了S集和T集

3、继续更新两个集合内的答案,按此方法递归下去

虽然dinic的时间复杂度是O(n^2*m),但注意到n指数级减小,所以不会T。这应该是道简单的模板题吧。

代码也很好懂的。



#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#define ll long long
#define N 860
using namespace std;
map<int,bool> flag;
int head[N],dis[N],vis[N],cur[N],a[N],b[N],n,m,num,x,S,T,cnt,u,v,sum,ans,y,z;
const int inf=1e9;
struct edge{
	int from,to,next,cap;
}e[200000+2000];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add_edge(int u,int v,int c){
	e[++cnt]=(edge){u,v,head[u],c};head[u]=cnt;
	e[++cnt]=(edge){v,u,head[v],c};head[v]=cnt;
}
bool bfs(){
	queue<int>q;
	memset(dis,-1,sizeof(dis));
	q.push(S);dis[S]=0;
	while(!q.empty()){
		int now=q.front();q.pop();
		for(int i=head[now];i;i=e[i].next)
			if(e[i].cap && dis[e[i].to]==-1){
				dis[e[i].to]=dis[now]+1;
				q.push(e[i].to);
			}
	}
	return dis[T]!=-1;
}
int dfs(int x,int f){
	if(x==T) return f;
	int w,used=0;
	for(int i=cur[x];i;i=e[i].next)
		if(dis[e[i].to]==dis[x]+1){
			w=f-used;
			w=dfs(e[i].to,min(w,e[i].cap));
			e[i].cap-=w;e[i ^ 1].cap+=w;
			if(e[i].cap) cur[x]=i;
			used+=w;
			if(used==f) return f;
		}
	if(!used) dis[x]=-1;
	return used;
}
void dinic(){
	while(bfs()){
		for(int i=1;i<=n;i++) cur[i]=head[i];
		sum+=dfs(S,inf);
	}
}
void work(int u){
	vis[u]=1;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(!vis[v] && e[i].cap) work(v);
	}
}
void solve(int l,int r){
	if(l==r) return;
	for(int i=2;i<=cnt;i+=2) e[i].cap=e[i ^ 1].cap=(e[i].cap+e[i ^ 1].cap)>>1;
	S=a[l];T=a[r];
	sum=0;
	dinic();
	if(!flag[sum]) ans++,flag[sum]=true;
	memset(vis,0,sizeof(vis));
	work(S);
	int num1=l,num2=r;
	for(int i=l;i<=r;i++){
		if(vis[a[i]]) b[num1++]=a[i];
		else b[num2--]=a[i];
	}
	for(int i=l;i<=r;i++) a[i]=b[i];
	solve(l,num1-1);solve(num1,r);
}
int main()
{
	n=read();m=read();
	cnt=1;
	for(int i=1;i<=m;i++){
		x=read();y=read();z=read();
		add_edge(x,y,z);
	}
	for(int i=1;i<=n;i++) a[i]=i;
	solve(1,n);
	printf("%d\n",ans);
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值