CF343E Pumping Stations 題解

rlc202204發表於2024-03-12

題意:

給定一張無向帶權圖,求一個排列 \(p\) 使得 \(\sum_{i=2}^n \operatorname{mincut}(p_{i - 1}, p_i)\) 最大。輸出一種方案。

\(n \le 200, m \le 1000\)

思路:

首先這種最小割相關的肯定是最小割樹,建樹需要 \(O(n^3 m)\),由於 \(Dinic\) 實際上跑不滿,所以時間完全夠。

然後考慮檢出最小割樹,轉化為對於一棵樹,求一個排列,\(val(i,j)\) 定義為 \(i\)\(j\) 路徑上最小邊權,使 \(\sum_{i=2}^nval(p_i, p_{i - 1})\) 最大。

對於這種問題,我們考慮構造一個上界,不難發現,最大邊權最多貢獻一次,沿著這個思路,我們猜測答案是恰好每條邊貢獻一次。

我們先構造一個方案,找到最小邊,然後遞迴兩邊處理,保證只有一次經過了這條最小邊即可。

考慮如何證明這是最優的。

對樹的節點數採用數學歸納法,我們找到最小的邊 \((u,v)\),顯然,經過 \((u,v)\) 一次以上不如只經過 \((u,v)\) 一次。所以我們可以發現最右情況 \((u,v)\) 只會被經過 \(1\) 次,而根據歸納法,被分開的兩個部分也是邊權值和為答案,所以我們就可以證出來這個結論。

然後就是最小割樹剪出來加上一個簡單的分治即可。

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 205;
const int inf = 0x3f3f3f3f;

int n, m;
struct Edge {
	int to, val, rev;
	Edge (int _to = 0, int _val = 0, int _rev = 0) :
		to(_to), val(_val), rev(_rev) {}
};
vector<Edge> e[N], eG[N];
void addEdge(int u, int v, int w) {
	eG[u].push_back(Edge(v, w, (int)eG[v].size()));
	eG[v].push_back(Edge(u, w, (int)eG[u].size() - 1));
}



int d[N] = {0};
bool bfs(int s, int t) {
	memset(d, inf, sizeof d);
	queue<int> q;
	q.push(s), d[s] = 0;
	while (!q.empty()) {
		int h = q.front();
		q.pop();
		for (auto i: e[h])
			if (i.val > 0 && d[i.to] > d[h] + 1) 
				d[i.to] = d[h] + 1, q.push(i.to);
	}
	return d[t] != inf;
}

int cur[N] = {0};
int dfs(int x, int t, int f) {
	if (x == t)
		return f;
	for (int i = cur[x]; i < (int)e[x].size(); i = ++cur[x])
		if (e[x][i].val > 0 && d[e[x][i].to] == d[x] + 1) {
			int fl = dfs(e[x][i].to, t, min(e[x][i].val, f));
			if (fl > 0) {
				e[x][i].val -= fl;
				e[e[x][i].to][e[x][i].rev].val += fl;
				return fl;
			}
		}
	d[x] = -1;
	return 0; 
}

int Dinic(int s, int t) {
	//init();
	int ans = 0;
	while (bfs(s, t)) {
		memset(cur, 0, sizeof cur);
		int ad = 0;
		while ((ad = dfs(s, t, inf)) != 0)
			ans += ad;
	}
	return ans;
}

void init() {
	for (int i = 1; i <= n; i++)
		e[i] = eG[i];
}

int a[N] = {0}, b[N] = {0};

struct Edge_Tree {
	int to, val;
	Edge_Tree (int _to = 0, int _val = 0) :
		to(_to), val(_val) {} 
};
vector<Edge_Tree> t[N];

void add(int u, int v, int w) {
	t[u].push_back(Edge_Tree(v, w));
	t[v].push_back(Edge_Tree(u, w));
}

int slv(int l, int r) {
	if (l + 1 >= r)
		return 0;
	int S = a[l], T = a[l + 1];
	init();
	int ans = Dinic(S, T);
	add(S, T, ans);
	int ll = l, rr = r;
	for (int i = l; i < r; i++)
		if (d[a[i]] != inf)
			b[ll++] = a[i];
		else
			b[--rr] = a[i];
	for (int i = l; i < r; i++)
		a[i] = b[i];
	ans += slv(l, ll) + slv(rr, r);
	return ans;
}

bool vis[N] = {false};

int ans[N] = {0}, len = 0;

void srh(int x, int pr, int &A, int &B, int &mn) {
	for (auto i: t[x])
		if (i.to != pr && !vis[i.to]) {
			if (mn > i.val) 
				mn = i.val, A = x, B = i.to;
			srh(i.to, x, A, B, mn);
		}
}

void mrk(int x) {
	vis[x] = true;
	for (auto i: t[x])
		if (!vis[i.to])
			mrk(i.to);
}

void getans(int x) {//在當前還沒有 vis 的 x 所在聯通塊找到答案 
	int A = 0, B = 0, mn = 2e9;
	srh(x, 0, A, B, mn);
	if (A + B == 0) 
		ans[++len] = x;
	else {
		vis[B] = true;
		getans(A);
		vis[B] = false;
		getans(B);
	}
	mrk(x);
} 

int main() {
	cin >> n >> m;
	for (int i = 1, u, v, w; i <= m; i++) {
		cin >> u >> v >> w;
		addEdge(u, v, w);
	}
	for (int i = 1; i <= n; i++)
		a[i] = i;
	cout << slv(1, n + 1) << endl;
	getans(1);
	for (int i = 1; i <= n; i++)
		cout << ans[i] << " ";
	cout << endl;
	return 0;
}