Loading

P4214 [CERC2015]Juice Junctions 题解

最小割树板子题。

一开始我用通常我写的 \(\text{Isap}\) 去试了试,只有 \(\text{65pts}\)

后面尝试使用 \(\text{Dinic}\),居然就有了 \(\text{100pts}\),跑了 \(14s\)

最后题解区说可以用 \(\text{EK}\) 跑,我试了一下,居然只跑了 \(\text{6s}\)

好吧,\(\text{EK}\) 的崛起了属于是。

可为啥 \(\text{Isap}\)\(\text{Dinic}\) 慢呢,令人极度疑惑。

思路

应该没有人不会最小割树吧。

我觉得能找到这题的应该都会最小割树。

所以这里我可以对这道题网络流做法的迷惑的复杂度,做一些感性理解。

由于 \(\text{EK}\) 是单次不断寻找可行边,然后我们即可发现,本题中每个点的度数小于等于三,即到汇点的可行边只有三条,而又限制了每条边的流量严格为一。

故其网络流的最大复杂度应该为 \(O(3n)\) ,就相当于 \(O(n)\) 了。

故总复杂度应为 \(O(n^2)\)

\(\text{dinic}\) 需要不断的使用 \(\text{dfs}\) 寻找,自然多了很多其他的情况,所以更劣一些。

\(\text{EK}\) 的代码吧:

#include <bits/stdc++.h>
using namespace std;

const int N = 3010;
const int M = 20000;
const int inf = 1e9 + 7;

int T , n , m , s , t , q , cnt , maxflow;
int id[N] , que[N] , vis[N] , tmp[N] , head[N];

struct edge
{
	int to , nxt , val , sum;
}e[M];

struct Pre
{
	int v , edge;
}pre[N];

inline int read()
{
	int asd = 0 , qwe = 1; char zxc;
	while(!isdigit(zxc = getchar())) if(zxc == '-') qwe = -1;
	while(isdigit(zxc)) asd = asd * 10 + zxc - '0' , zxc = getchar();
	return asd * qwe;
}

inline void add(int x , int y , int z)
{
	e[++cnt] = {y , head[x] , z , z} , head[x] = cnt;
	e[++cnt] = {x , head[y] , 0 , 0} , head[y] = cnt;
}

inline int bfs()
{
	queue<int> q; 
	memset(que , 0 , sizeof que);
	memset(pre , -1 , sizeof pre);
	q.push(s) , que[s] = 1;
	while(q.empty() == 0)
	{
		int x = q.front(); q.pop();
		for(int i = head[x];i;i = e[i].nxt)
			if(que[e[i].to] == 0 && e[i].val)
			{
				que[e[i].to] = 1;
				pre[e[i].to] = (Pre){x , i};
				if(e[i].to == t) return 1;
				q.push(e[i].to);
			}
 	}
 	return 0;
}

inline void init()
{
	for(int i = 0;i <= cnt;i++) e[i].val = e[i].sum;
}

inline int EK(int ss , int ts)
{
	s = ss , t = ts , maxflow = 0 , init();
	while(bfs())
	{
		int minn = inf;
		for(int i = t;i != s;i = pre[i].v) minn = min(minn , e[pre[i].edge].val);
		for(int i = t;i != s;i = pre[i].v) e[pre[i].edge].val -= minn;
		maxflow += minn;
	}
	return maxflow;
}

struct GHT
{
	edge tr[M];
	int tot , hed[N] , ans[N][N];
	
	inline void add_tree(int x , int y , int z)
	{
		tr[++tot] = {y , hed[x] , z} , hed[x] = tot;
		tr[++tot] = {x , hed[y] , z} , hed[y] = tot;
	}
	
	inline void find(int now)
	{
		vis[now] = 1;
		for(int i = head[now];i;i = e[i].nxt)
			if(vis[e[i].to] == 0 && e[i].val) find(e[i].to);
	}
	
	inline void build(int l ,int r)
	{
		if(l >= r) return;
		int x = id[l] , y = id[l + 1] , sum = EK(x , y);
		int ls = l , rs = r;
		memset(vis , 0 , sizeof vis) , find(x);
		add_tree(x , y , sum);
		for(int i = l;i <= r;i++)
			tmp[(vis[id[i]] ? ls++ : rs--)] = id[i];
		for(int i = l;i <= r;i++) id[i] = tmp[i];
		build(l , ls - 1) , build(rs + 1 , r);
	}
	
	inline void get(int now , int fa , int dis , int gen)
	{
		ans[gen][now] = dis;
		for(int i = hed[now];i;i = tr[i].nxt)
			if(tr[i].to != fa) get(tr[i].to , now , min(dis , tr[i].val) , gen);
	}
	
	inline void solve()
	{
		for(int i = 1;i <= n;i++) id[i] = i;
		build(1 , n);
		for(int i = 1;i <= n;i++) get(i , 0 , inf , i);
	}
	
	inline int ask()
	{
		int res = 0;
		for(int i = 1;i <= n;i++)
			for(int j = i + 1;j <= n;j++) res += ans[i][j];
		return res;
	}
}tree;

int main()
{
	n = read() , m = read() , cnt = 1;
	for(int i = 1;i <= m;i++)
	{
		int x = read() , y = read();
		add(x , y , 1) , add(y , x , 1);
	}
	tree.solve();
	cout << tree.ask();
	return 0;
} 
posted @ 2022-02-28 11:42  JiaY19  阅读(33)  评论(0编辑  收藏  举报