题解:P6898 [ICPC 2014 WF] Metal Processing Plant

一个需要观察优化的小 trick。

题意:给出一个完全图和每条边边权,要求把所有点划分成两个点集 \(A,B\),定义 \(D(A)=\max_{x,y\in A}d(x,y)\),求 \(D(A)+D(B)\) 最小值。\(n\le 200\)

做法:

直接枚举 \(D(A)\) 再二分,用 2-sat 判定可以做到 \(O(n^4\log n)\)。用双指针枚举 \(D(B)\) 可以做到 \(O(n^4)\)

考虑怎么优化,先不妨假设 \(D(A)\ge D(B)\)。考虑 \(D(A)\) 变小的时候会有什么变化,注意到 \(>D(A)\) 在 2-sat 的边是双向的,我们不妨直接用并查集缩起来。考虑如果出现了一个奇环,那么在之后就直接爆掉了,所以我们对这条边计算答案之后,后面的就没有必要再计算了,因为一定不合法;如果出现了偶环,那么我们需要这个端点是同色的,但是中间就会出现同色,这样这种情况就不算入答案。而如果不出现环就跑,这样只需要对 \(O(n)\)\(D(A)\) 处理。注意别用双指针要不然退化成 \(O(n^4)\) 了,总复杂度 \(O(n^3\log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 205 + 5;
int n, d[maxn][maxn], pre[maxn * 2];
struct Edge {
	int x, y, w;
	friend bool operator<(Edge x, Edge y) {
		return (x.w != y.w ? x.w < y.w : x.x < y.x);
	}
};
vector<Edge> ed;
void prepare() {
	for (int i = 1; i <= 2 * n; i++)
		pre[i] = i;
}
int fnd(int x) {
	return (pre[x] == x ? x : pre[x] = fnd(pre[x]));
}
int unn(int x, int y) {
	if(fnd(x) == fnd(y + n))
		return 0;
	pre[fnd(x)] = fnd(y + n);
	pre[fnd(y)] = fnd(x + n);
	if(fnd(x) == fnd(x + n))
		return -1;
	return 1;
}
int ans = 9e18;
vector<int> e[maxn * 2];
int dfn[maxn * 2], low[maxn * 2], st[maxn * 2], top, tot, inst[maxn * 2], pos[maxn * 2], cnt;
void tarjan(int u) {
	dfn[u] = low[u] = ++tot, st[++top] = u; inst[u] = 1;
	for (int i = 0; i < e[u].size(); i++) {
		int v = e[u][i];
		if(!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(inst[v])
			low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]) {
		cnt++;
		while(st[top + 1] != u) {
			int p = st[top--];
			pos[p] = cnt, inst[p] = 0;
		}
	}
}
bool chk(int x, int y) {
	for (int i = 1; i <= 2 * n; i++)
		e[i].clear(), dfn[i] = low[i] = 0, pos[i] = 0;
	top = tot = cnt = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= n; j++) {
			if(d[i][j] > x)	
				e[i].push_back(j + n), 
				e[j].push_back(i + n);
			if(d[i][j] > y)
				e[i + n].push_back(j),
				e[j + n].push_back(i);
		}
	}
	for (int i = 1; i <= 2 * n; i++)
		if(!dfn[i]) {
			for (int j = 1; j <= 2 * n; j++)
				st[j] = 0;
			tarjan(i);
		}
	for (int i = 1; i <= n; i++)
		if(pos[i] == pos[i + n])
			return 0;
	return 1;
}
signed main() {
	// freopen("test.in", "r", stdin);
	// freopen("baoli.out", "w", stdout);
	cin >> n;
	if(n <= 2) {
		cout << 0 << endl;
		return 0;
	}
	for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++)
			cin >> d[i][j], d[j][i] = d[i][j],
			ed.push_back(Edge{i, j, d[i][j]});
	ed.push_back(Edge{0, 0, 0});
	sort(ed.begin(), ed.end());
	prepare();
	for (int i = ed.size() - 1; i >= 0; i--) {
		if(i != ed.size() - 1) {
			int f = unn(ed[i + 1].x, ed[i + 1].y);
			if(f == 0)
				continue;
			if(f == -1) {
				i++;
				int l = -1, r = ed[i].w;
			//	cout << "adf" << endl;
				while(l + 1 < r) {
					int mid = l + r >> 1;
					if(chk(ed[i].w, mid))
						r = mid;
					else
						l = mid;
				}
			//	cout << ed[i].w << " " << r << " " << i << endl;
				if(chk(ed[i].w, r)) {
					ans = min(ans, ed[i].w + r);
				//	if(n == 200)
				//		cout << ed[i].w << " " << r << endl;
				}
				break;
			}
		}
		int l = -1, r = ed[i].w;
	//	cout << "adf" << endl;
		while(l + 1 < r) {
			int mid = l + r >> 1;
			if(chk(ed[i].w, mid))
				r = mid;
			else
				l = mid;
		}
	//	cout << ed[i].w << " " << r << " " << i << endl;
		if(chk(ed[i].w, r)) {
			ans = min(ans, ed[i].w + r);
		//	if(n == 200)
		//		cout << ed[i].w << " " << r << endl;
		}
		i++;
		if(i != ed.size()) {
			l = -1, r = ed[i].w;
		//	cout << "adf" << endl;
			while(l + 1 < r) {
				int mid = l + r >> 1;
				if(chk(ed[i].w, mid))
					r = mid;
				else
					l = mid;
			}
		//	cout << ed[i].w << " " << r << " " << i << endl;
			if(chk(ed[i].w, r)) {
				ans = min(ans, ed[i].w + r);
			//	if(n == 200)
			//		cout << ed[i].w << " " << r << endl;
			}
		}
		i--;
	}
	cout << ans << endl;
	return 0;
}
/*

*/
posted @ 2025-11-17 22:06  LUlululu1616  阅读(5)  评论(0)    收藏  举报