【bzoj2229】 Zjoi2011—最小割

http://www.lydsy.com/JudgeOnline/problem.php?id=2229 (题目链接)

题意

  给出一张无向图,$q$组询问,每次询问最小割不大于$c$的点对数量。

Solution

  orz:DaD3zZ

  最小割树什么的好神,但是看不懂啊,不如直接撸代码= =。根据网上神犇的理论,貌似最小割的数目不会超过$n-1$个,所以可以将它构成一棵最小割树。

  不过我们的实现并不需要考虑怎么构树。直接暴力的话就是枚举点对,要做$n^2$次$Dinic$,我们通过选择一些优秀的点对来减少$Dinic$的次数。每次分治,任选两个在当前分治区间中的点作为源点和汇点,在原图上做一次$Dinic$,将原图分为了两个割集$S$和$T$,更新$S$和$T$之间的点的最小割。将这两个割集与分治区间取交得到分值区间的割集$S‘$和$T‘$,然后递归处理$S‘$和$T‘$就可以了。

  值得注意的是,这样子并没有减小问题的规模,只是通过有技巧的选择源点和汇点来减少$Dinic$的次数(虽然我也不知道为什么这样是正确的)。复杂度大概是$O(kn*Dinic)$,$k$这个常数应该不会太大,出题人总不会丧心病狂卡这玩意儿吧,大不了random_shuffle一下= =。

细节

  无向图。

代码

// bzoj2229
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define LL long long
#define inf (1ll<<30)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;

const int maxn=200,maxm=10010;
int Q,n,m,cnt,id[maxn],head[maxn],ans[maxn][maxn],vis[maxn],tmp[maxn];
struct edge {int to,next,w;}e[maxm];

namespace Dinic {
	int d[maxn],S,T;
	void link(int u,int v,int w) {
		e[++cnt]=(edge){v,head[u],w};head[u]=cnt;
		e[++cnt]=(edge){u,head[v],w};head[v]=cnt;
	}
	bool bfs() {
		memset(d,-1,sizeof(d));
		queue<int> q;q.push(S);d[S]=0;
		while (!q.empty()) {
			int x=q.front();q.pop();
			for (int i=head[x];i;i=e[i].next)
				if (e[i].w && d[e[i].to]<0) d[e[i].to]=d[x]+1,q.push(e[i].to);
		}
		return d[T]>0;
	}
	int dfs(int x,int f) {
		if (x==T || f==0) return f;
		int w,used=0;
		for (int i=head[x];i;i=e[i].next) if (e[i].w && d[e[i].to]==d[x]+1) {
				w=dfs(e[i].to,min(e[i].w,f-used));
				used+=w,e[i].w-=w,e[i^1].w+=w;
				if (used==f) return used;
			}
		if (!used) d[x]=-1;
		return used;
	}
	int main(int x,int y){
		S=x,T=y;int flow=0;
		while (bfs()) flow+=dfs(S,inf);
		return flow;
	}
}
using namespace Dinic;

void Init() {
	cnt=1;
	memset(head,0,sizeof(head));
	memset(ans,0x7f,sizeof(ans));
}
void dfs(int x) {
	vis[x]=1;
	for (int i=head[x];i;i=e[i].next)
		if (e[i].w && !vis[e[i].to]) dfs(e[i].to);
}
void solve(int L,int R) {
	if (L==R) return;
	for (int i=2;i<=cnt;i+=2) e[i].w=e[i^1].w=(e[i].w+e[i^1].w)>>1;
	int flow=Dinic::main(id[L],id[R]);
	memset(vis,0,sizeof(vis));dfs(id[L]);
	for (int i=1;i<=n;i++) {
		if (!vis[i]) continue;
		for (int j=1;j<=n;j++)
			if (!vis[j]) ans[i][j]=ans[j][i]=min(ans[i][j],flow);
	}
	int l=L,r=R;
	for (int i=L;i<=R;i++) vis[id[i]] ? tmp[l++]=id[i] : tmp[r--]=id[i];
	for (int i=L;i<=R;i++) id[i]=tmp[i];
	solve(L,l-1);solve(r+1,R);
}

int main() {
	int T;scanf("%d",&T);
	while (T--) {
		scanf("%d%d",&n,&m);Init();
		for (int i=1;i<=n;i++) id[i]=i;
		for (int u,v,w,i=1;i<=m;i++) {
			scanf("%d%d%d",&u,&v,&w);
			Dinic::link(u,v,w);
		}
		solve(1,n);
		scanf("%d",&Q);
		for (int c,i=1;i<=Q;i++) {
			scanf("%d",&c);int res=0;
			for (int j=1;j<=n;j++)
				for (int k=j+1;k<=n;k++) if (ans[j][k]<=c) res++;
			printf("%d\n",res);
		}
		puts("");
	}
	return 0;
}
时间: 2024-10-10 22:30:01

【bzoj2229】 Zjoi2011—最小割的相关文章

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

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

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

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

[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的割. 对于带权图来说,将所有顶点处在不同部分的边的

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>

【BZOJ2229】【ZJOI2011】最小割

冷门知识点-- 原题: 小白在图论课上学到了一个新的概念--最小割,下课后小白在笔记本上写下了如下这段话: "对于一个图,某个对图中结点的划分将图中所有结点分成两个部分,如果结点s,t不在同一个部分中,则称这个划分是关于s,t的割. 对于带权图来说,将所有顶点处在不同部分的边的权值相加所得到的值定义为这个割的容量,而s,t的最小割指的是在关于s,t的割中容量最小的割" 现给定一张无向图,小白有若干个形如"图中有多少对点它们的最小割的容量不超过x呢"的疑问,小蓝虽然很