【BZOJ2229】【ZJOI2011】最小割 {没有错,这道题的算法跟题帽是一样的!!!}

题解:分治求最小割。

【l……r】里任意找两个作为s、t(不妨把s设为l位置上的点,t设为r位置上的点)求最小割,两层for循环枚举修改map[i][j]即两点间最小割值。

然后一部分属于S集,一部分属于T集,分治【l,L】,【R,r】,每次求完最小割值都全局进行修改。

最后每次询问暴力做就好了,无需任何优化即可AC。

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 200
#define M 7000
#define inf 0x3f3f3f3f
#define ci crs[i]
#define cj crs[j]
using namespace std;
struct KSD
{
	int v,len,next,flow;
}e[M];
int head[N],cnt;
inline void add(int u,int v,int len)
{
	cnt++;
	e[cnt].v=v;
	e[cnt].flow=e[cnt].len=len;
	e[cnt].next=head[u];
	head[u]=cnt;
}
inline void resume(){for(int i=2;i<=cnt;i++)e[i].len=e[i].flow;}

int crs[N],tcrs[N];
bool flag[N];
int map[N][N];

int d[N],s,t;
int n,m;
queue<int>q;

bool bfs()
{
	int i,u,v;
	memset(d,0,sizeof(d));
	while(!q.empty())q.pop();
	q.push(s),d[s]=1;
	while(!q.empty())
	{
		u=q.front(),q.pop();
		for(i=head[u];i;i=e[i].next)
		{
			v=e[i].v;
			if(e[i].len&&!d[v])
			{
				d[v]=d[u]+1;
				if(v==t)return 1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dinic(int x,int flow)
{
	if(x==t)return flow;
	int remain=flow,i,v,k;
	for(i=head[x];i&&remain;i=e[i].next)
	{
		if(d[v=e[i].v]==d[x]+1&&e[i].len)
		{
			k=dinic(v,min(remain,e[i].len));
			if(!k)d[v]=0;
			e[i].len-=k,e[i^1].len+=k;
			remain-=k;
		}
	}
	return flow-remain;
}
void dfs(int x)
{
	int i,v;
	flag[x]=1;
	for(i=head[x];i;i=e[i].next)if(!flag[v=e[i].v]&&e[i].len)dfs(v);
}
void dar(int l,int r) // divide and rule 分治
{
	int i,j,maxflow=0;
	s=crs[l],t=crs[r];
	resume();
	while(bfs())maxflow+=dinic(s,inf);
	memset(flag,0,sizeof(flag)),dfs(s);

	for(i=1;i<=n;i++)for(j=1;j<=n;j++)
		if(flag[ci]^flag[cj])
			map[ci][cj]=min(map[ci][cj],maxflow);

	int L=l-1,R=r+1;
	for(i=l;i<=r;i++)(flag[ci]?tcrs[++L]:tcrs[--R])=ci;
	for(i=l;i<=r;i++)crs[i]=tcrs[i];

	if(l<L)dar(l,L);
	if(R<r)dar(R,r);
}
int main()
{
	int i,j,k,g;
	int a,b,c;
	for(scanf("%d",&g);g--;)
	{
		cnt=1;
		memset(head,0,sizeof(head));
		memset(map,0x3f,sizeof(map));
		for(scanf("%d%d",&n,&m),cnt=1;m--;)
		{
			scanf("%d%d%d",&a,&b,&c);
			add(a,b,c),add(b,a,c);
		}
		for(i=1;i<=n;i++)crs[i]=i;
		dar(1,n);
		for(scanf("%d",&m);m--;)
		{
			scanf("%d",&k);
			int ans=0;
			for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(map[i][j]<=k)ans++;
			printf("%d\n",ans>>1);
		}
		puts("");
	}
	return 0;
}

话说我再贴个错误思想的代码吧

:SW算法求全局最小割,然后两边任意点对间的最小割值肯定都是这个值,然后两边分治。

SW算法相关博客(点此即链接):话说我学SW就是想用来解决这个的233……

这个很有正确性的样子嘛,为什么会挂呢?

就姑且留给读者思考吧,这个是确实有问(fan)题(li)的(跟SW算法实现过程以及分治后的一些值有关系)

代码(WA):

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 160
#define inf 0x3f3f3f3f
using namespace std;
int n,m,q,crs[N],tcrs[N],cnt;
struct ANS
{
	int x,sum;
	ANS(int _x=0,int _sum=0):x(_x),sum(_sum){}
	bool operator < (const ANS &a)const{return x<a.x;}
}ans[N];
int map[N][N];

int dis[N],f[N];
bool vis[N],flag[N];
int next[N],final[N];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void Stoer_Wagner(int l,int r)
{
	int ret=inf;
	int i,j,_n,rnd;
	int u,v,temp,lastu=0;
	for(i=l;i<=r;i++)f[i]=next[i]=final[i]=i,flag[i]=0;
	for(_n=r-l+1;_n>1;_n--)
	{
		for(i=l;i<=r;i++)dis[i]=vis[i]=0;
		for(rnd=1;rnd<=_n;rnd++)
		{
			lastu=u;
			temp=-1;
			for(i=l;i<=r;i++)if(f[i]==i&&!vis[i])
			{
				if(temp<dis[i])temp=dis[i],u=i;
			}
			vis[u]=1;
			bool flag=0;
			for(j=u;;j=next[j])
			{
				if(j==final[u])flag=1;
				for(i=l;i<=r;i++)if(map[crs[u]][crs[i]])
				{
					v=find(i);
					if(v!=u)dis[v]+=map[crs[u]][crs[i]];
				}
				if(flag)break;
			}
		}
		if(ret>=dis[u])
		{
			for(i=l;i<=r;i++)flag[i]=(f[i]!=i||i==u);
			ret=dis[u];
		}
		f[u]=lastu;
		next[final[lastu]]=u;
		final[lastu]=final[u];
	}
	int L=l-1,R=r+1;
	for(i=l;i<=r;i++)
	{
		if(flag[i])tcrs[++L]=crs[i];
		else tcrs[--R]=crs[i];
	}
	for(i=l;i<=r;i++)crs[i]=tcrs[i];
	ans[++cnt]=ANS(ret,(R-l)*(r-L));
	if(R-l>1)Stoer_Wagner(l,L);
	if(r-L>1)Stoer_Wagner(R,r);
	return ;
}
int sum[N];
int main()
{
//  freopen("test.in","r",stdin);
	int i,k,g;
	int a,b,c;
	for(scanf("%d",&g);g--;)
	{
		memset(map,0,sizeof(map));
		scanf("%d%d",&n,&m);
		for(i=1;i<=n;i++)crs[i]=i;
		for(i=1;i<=m;i++)
		{
			scanf("%d%d%d",&a,&b,&c);
			if(a!=b)map[a][b]+=c,map[b][a]+=c;
		}
		Stoer_Wagner(1,n);
		sort(ans+1,ans+cnt+1);
		for(i=1;i<=cnt;i++)sum[i]=sum[i-1]+ans[i].sum;

		scanf("%d",&q);
		while(q--)
		{
			scanf("%d",&k);
			int l=1,r=cnt,mid,ret;
			while(l<r)
			{
				if(r-l<=3)
				{
					ret=0;
					for(i=l;i<=r;i++)if(ans[i].x<=k)ret=i;
					break;
				}
				mid=l+r>>1;
				if(ans[i].x<=k)l=mid;
				else r=mid-1;
			}
			printf("%d\n",sum[ret]);
		}
	}
	return 0;
}
时间: 2024-10-12 21:09:19

【BZOJ2229】【ZJOI2011】最小割 {没有错,这道题的算法跟题帽是一样的!!!}的相关文章

[bzoj2229][Zjoi2011]最小割_网络流_最小割树

最小割 bzoj-2229 Zjoi-2011 题目大意:题目链接. 注释:略. 想法: 在这里给出最小割树的定义. 最小割树啊,就是这样一棵树.一个图的最小割树满足这棵树上任意两点之间的最小值就是原图中这两点之间的最小割. 这个性质显然是非常优秀的. 我们不妨这样假设,我么已经把最小割树求出来了,那么这个题就迎刃而解了. 我们可以直接枚举点对,然后暴力验证就可以直接枚举出所有的合法点对是吧. 那么问题来了,我们如何才能求出所有的合法的点对? 这就需要用到了最小割树的构建过程. 我们最小割树的构

bzoj2229: [Zjoi2011]最小割(分治最小割+最小割树思想)

2229: [Zjoi2011]最小割 题目:传送门 题解: 一道非常好的题目啊!!! 蒟蒻的想法:暴力枚举点对跑最小割记录...绝对爆炸啊.... 开始怀疑是不是题目骗人...难道根本不用网络流???一看路牌....分治最小割?最小割树? 然后开始各种%论文... 简单来说吧,根据各种本蒟蒻不会证明的理论,那么:所有最小割都不是完全独立的,总共有n-1种(也就是树上的n-1条边)最小割 恰好和树的定义一样啊! 那么用一个solve递归函数来解决,一开始任意找两个点作为st和ed来最小割,然后分

[BZOJ2229][ZJOI2011]最小割

bzoj luogu sol 最小割树请转一道很相似完全一模一样的题 所以跑出所有点对之间的最小割然后暴力统计答案即可. code #include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; int gi() { int x=0,w=1;char ch=getchar(); while ((ch<'0'||ch>'9')&a

bzoj2229: [Zjoi2011]最小割(最小割树)

传送门 这题是用最小割树做的(不明白最小割树是什么的可以去看看这一题->这里) 有了最小割树就很简单了……点数那么少……每次跑出一个最大流就暴力搞一遍就好了 1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 #include<queue> 7 #define inf 0x3f3f3f3f 8 usi

【BZOJ-2229】最小割 最小割树(最大流+分治)

2229: [Zjoi2011]最小割 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 1565  Solved: 560[Submit][Status][Discuss] Description 小白在图论课上学到了一个新的概念——最小割,下课后小白在笔记本上写下了如下这段话: “对于一个图,某个对图中结点的划分将图中所有结点分成两个部分,如果结点s,t不在同一个部分中,则称这个划分是关于s,t的割. 对于带权图来说,将所有顶点处在不同部分的边的

【bzoj2229】 Zjoi2011—最小割

http://www.lydsy.com/JudgeOnline/problem.php?id=2229 (题目链接) 题意 给出一张无向图,$q$组询问,每次询问最小割不大于$c$的点对数量. Solution orz:DaD3zZ 最小割树什么的好神,但是看不懂啊,不如直接撸代码= =.根据网上神犇的理论,貌似最小割的数目不会超过$n-1$个,所以可以将它构成一棵最小割树. 不过我们的实现并不需要考虑怎么构树.直接暴力的话就是枚举点对,要做$n^2$次$Dinic$,我们通过选择一些优秀的点

BZOJ 2229 ZJOI2011 最小割 最小割+分治 400AC达成&amp;&amp;2000Submission达成

题目大意:给定一个图,多次询问有多少个点对之间的最小割小于等于某个值 最小割分治- - 首先朴素的想法是做O(n^2)遍网络流 但是这样显然是过不去的 根据一些结论,最小割最多有n-1个,这n-1个最小割构成一个最小割树 别问我为什么- - 因此我们分治寻找这n-1个最小割 每层分治,先任选两个点作为源汇做一遍最小割 然后找出S集和T集,对所有S集的点和T集的点构成的点对用本次得到的最小割更新一遍 注意更新的是全部S集和全部T集,不只是本次分治内部的S集和T集 然后将本次分治的点分成S集和T集,

[ZJOI2011] 最小割 - 最小割树

最小割树裸题 建树后,以每个点为根跑DFS求出距离矩阵,然后暴力回答询问即可 #include <bits/stdc++.h> using namespace std; #define int long long const int maxn=6e2; const int maxm=4e4; const int inf=1e13; int n,m,q; //for the target graph vector <pair<int,int> > g[maxn]; voi

[ZJOI2011]最小割(最小割树模板)

https://www.luogu.com.cn/problem/P3329 最小割树的用处不仅是做这些裸题,了解这个定理,会对一类问题有更深的思考. 最小割树的实现: 每次取两个点u,v,求它们的割,并在最小割树上给它们连边,权值为这个割. 然后按照S能走到的和能走到T的,分成两类点,继续递归建树. 原图中的两个点的最小割,即为树上边权的最小值. 容易证明最小割<=树上边权的最小值,但是要证明恰好是,比较困难,博主暂时不会. Code: #include<bits/stdc++.h>