[ZJOI2011] 最小割

一、前言

最小割树练习板题,浓度更纯的板题就不写题解了吧,虽然都挺板的。

注意本博客只讲方法不讲证明,原因下面有。

二、题目

洛谷

三、讲解

(一)、坑点

首先注意到这件事:

在两组测试数据之间需要输出一行空行。

然后我们开始讲。

(二)、科技:最小割树

这小标题有种3A大作的感觉。

首先我们要定义无向图中两个点的最小割:使得它们不连通要割的边的最小权值和。

存在这么一棵带边权的树:点与点之间路径上权值的最小值为这两个点在原图中的最小割,我们称这棵树为最小割树。

构造方法是每次随便挑两个点作为源汇点跑最小割,然后连树边,权值就是这个最小割,把源汇点两边的点分别递归下去做,可以发现要跑 \(O(n)\) 次网络流,一般来说时间的瓶颈就在这里。

至于为啥存在这样一棵树,以及为啥这样构造,这样构造为啥是对的?一律不解释,问就是懒。

想了解可以看2016年的集训队论文《浅谈无向图最小割问题的一些算法和应用》,应该比我讲得更权威更清楚。没想到吧,这就是我偷懒的借口!

既然建树的复杂度都这么高了,往往询问都可以直接在这棵树上暴力做,因此题目都比较板。

(三)、正题

建出最小割树之后,求出两两之间最小割,然后每次询问 \(O(n^2)\) 回答即可。

时间复杂度 \(O(n^3m+qn^2)\)

四、代码

//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 155;
const int MAXM = 3005;
const LL INF = 1ll << 60;
int n,m;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int head[MAXN],tot = 1;
struct edge
{
	int v,w,nxt;
}e[MAXM<<1];
void Add_Edge(int u,int v,int w)
{
	e[++tot] = edge{v,w,head[u]};
	head[u] = tot;
}
void Add_Double_Edge(int u,int v,int w) 
{
	Add_Edge(u,v,w);
	Add_Edge(v,u,w);
}

int dis[MAXN],cur[MAXN];
bool bfs(int S,int T)
{
	for(int i = 1;i <= n;++ i) dis[i] = n+1;
	queue<int> q; q.push(S); dis[S] = 0;
	while(!q.empty())
	{
		int x = q.front(); q.pop();
//		printf("bfs %d %d %d\n",x,S,T);
		for(int i = head[x],v; i ;i = e[i].nxt)
			if(e[i].w && dis[x]+1 < dis[v = e[i].v]) dis[v] = dis[x]+1,q.push(v);
	}
	return dis[T] <= n;
}
LL dfs(int x,LL flow,int T)
{
	if(x == T) return flow;
	LL ret = 0;
	for(int &i = cur[x],v; i ;i = e[i].nxt)
		if(dis[x]+1 == dis[v = e[i].v] && e[i].w)
		{
			LL dz = dfs(v,Min(0ll+e[i].w,flow-ret),T);
			e[i].w -= dz; e[i^1].w += dz;
			if((ret += dz) == flow) break;
		}
	return ret;
}
LL dinic(int S,int T)
{
	for(int i = 2;i <= tot;i += 2) e[i^1].w = e[i].w = (0ll + e[i].w + e[i^1].w) >> 1;
	LL ret = 0;
	while(bfs(S,T)) 
	{
		for(int i = 1;i <= n;++ i) cur[i] = head[i]; 
		ret += dfs(S,INF,T);
	} 
	return ret;
}

vector<pair<int,LL> > G[MAXN];
int ID[MAXN];
bool vis[MAXN];
void findp(int x)
{
	vis[x] = 1;
	for(int i = head[x],v; i ;i = e[i].nxt)
		if(e[i].w && !vis[v = e[i].v]) 
			findp(v);
}
void solve(int l,int r)
{
	if(l >= r) return;
	LL mf = dinic(ID[l],ID[l+1]);
	G[ID[l]].emplace_back(make_pair(ID[l+1],mf));
	G[ID[l+1]].emplace_back(make_pair(ID[l],mf));
	for(int i = 1;i <= n;++ i) vis[i] = 0;
	findp(ID[l]);
	sort(ID+l,ID+r+1,[](int x,int y){return vis[x] < vis[y];});
	for(int i = l;i <= r;++ i)
		if(vis[ID[i]]) {solve(l,i-1);solve(i,r);break;}
}
int rt;
LL cut[MAXN][MAXN];
void dfs2(int x,int fa,LL d)
{
	cut[rt][x] = d;
	for(auto &A : G[x])
		if(A.first != fa)
			dfs2(A.first,x,Min(d,A.second));
}

int main()
{
//	freopen("10.in","r",stdin);
//	freopen("mine.out","w",stdout);
	for(int T = Read(); T ;-- T)
	{
		n = Read(); m = Read(); tot = 1;
		for(int i = 1;i <= n;++ i) head[i] = 0,G[i].clear(),ID[i] = i;
		for(int i = 1,u,v;i <= m;++ i) u = Read(),v = Read(),Add_Double_Edge(u,v,Read());
		solve(1,n);
		for(int i = 1;i <= n;++ i) rt = i,dfs2(i,0,INF);
		for(int Q = Read(); Q ;-- Q)
		{
			LL val = Read(),ans = 0;
			for(int i = 1;i <= n;++ i)
				for(int j = i+1;j <= n;++ j)
					if(cut[i][j] <= val) ++ans;
			Put(ans,'\n');
		}
		putchar('\n');
	}
	return 0;
}
posted @ 2022-02-21 22:47  皮皮刘  阅读(27)  评论(0编辑  收藏  举报