CF587 D Duff in Mafia (2-sat)

题意

给一张无向图,每条边有颜色和边权。选择一个匹配删去,使得每个点相邻的所有边没有颜色相同的。最小化删去匹配的最大边权并输出方案。
n , m 5 × 1 0 4 n, m\leq 5\times 10^4

思路

  • 首先简单转化成2-sat问题。
  • 然后二分答案,现在是钦点了一些边不选,判断是否有方案即可。

2-sat问题基于对称性的解法

  • 先将每条边拆成x与x’,表示选或不选。然后将所有形如选了a就一定要选b的关系连上一条有向边<a,b>。
  • (下面x’指的是x的反点。)
  • 建一些虚点辅助连边,不影响整个图的连通性
  • 跑tarjan,若x与x’在同一个连通分量里面,则无解。
  • 这个图很对称,若x能到达y,则y’能到达x’.
  • 因此将所有强制选的点在dag上一路选下去,并将走到的所有点的反点标记不选。
  • 若已有反点标记选,则无解。
  • 否则,再按照拓扑序逆序选择每个还未确定的点做上述选的过程即可。(无解的情况都存在x走到x’,此时由于是按照拓扑序逆序选的,因此必定是有解的。)
  • 其实强制一个点选,只要其反点向其连边即可。(不过这样要重建,很慢)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
pair<int, int> key[N];
int tim[N], fx[N];
map<int, int> cnt[N];
vector<int> to[N], zto[N];
void link(int x, int y) {
	// printf("%d %d\n", x, y);
	to[x].push_back(y);
}

int zz;
void add(int u, int c, int i) {
	if (cnt[u][c] != 0) {
		if (cnt[u][c] == -1) {
			printf("No\n"); exit(0);
		}
		link(i + m, cnt[u][c]);
		link(cnt[u][c] + m, i);
		cnt[u][c] = -1;
	} else {
		cnt[u][c] = i;
	}
}

int dfn[N], low[N], stm;
int S[N], ins[N], cir[N], tot;
vector<int> ps[N];
void tarjan(int x) {
	dfn[x] = low[x] = ++ stm;
	S[++S[0]] = x; ins[x] = 1;
	for(int y : to[x]) {
		if (dfn[y]) {
			if (ins[y]) low[x] = min(low[x], low[y]);
		} else {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
	}
	if (low[x] == dfn[x]) {
		do {
			ins[S[S[0]]] = 0;
			cir[S[S[0]]] = x;
			if (S[S[0]] <= 2 * m)
				ps[x].push_back(S[S[0]]);
		} while(S[S[0]--] != x);
	}
}

int d[N], Q[N];
vector<int> es[N];
void topo() {
	int h = 0, t = 0;
	for(int i = 1; i <= tot; i++) if (cir[i] == i && d[i] == 0) Q[++t] = i;
	while (h < t) {
		int x = Q[++h];
		for(int y : zto[x]) {
			if(--d[y] == 0) Q[++t] = y;
		}
	}
	Q[0] = h;
}

int vis[N];
bool enable(int x) {
	if (vis[x] == -1) return 0;
	if (vis[x] == 1) return 1;
	vis[x] = 1;
	for(int e : ps[x]) {
		if (vis[cir[fx[e]]] == 1) return 0;
		vis[cir[fx[e]]] = -1;
	}
	for(int y : zto[x]) if(!enable(y)) return 0;
	return 1;
}

bool ok(int x) {
	memset(vis, 0, sizeof vis);
	for(int i = 1; i <= m; i++) if (tim[i] > x) {
		if (!enable(cir[i + m])) return 0;
	}
	for(int i = Q[0]; i; i--) {
		int x = Q[i];
		if (x > 2 * m) {
			if (ps[x].size() == 0) continue;
			x = ps[x].back();
		}
		if (vis[cir[x]] == 0) {
			enable(cir[x]);
		}
	}
	return 1;
}

int main() {
	freopen("d.in","r",stdin);
	cin >> n >> m; tot = 2 * m;
	for(int i = 1; i <= m; i++) {
		int u, v, c, t; scanf("%d %d %d %d", &u, &v, &c, &t);
		tim[i] = t;
		es[u].push_back(i);
		es[v].push_back(i);
		add(u, c, i);
		add(v, c, i);
	}
	for(int i = 1; i <= n; i++) {
		int pre = 0;
		for(int j : es[i]) {
			int u = ++tot;
			if (pre) {
				link(j, pre);
				link(u, pre);
			}
			link(u, j + m);
			pre = u;
		}
		pre = 0;
		for (int z = es[i].size() - 1; ~z; z--) {
			int j = es[i][z];
			int u = ++tot;
			if (pre) {
				link(j, pre);
				link(u, pre);
			}
			link(u, j + m);
			pre = u;
		}
	}
	for(int i = 1; i <= tot; i ++) if (!dfn[i])
		tarjan(i);
	for(int i = 1; i <= m; i++) {
		fx[i]     = i + m;
		fx[i + m] = i;
		if (cir[i] == cir[i + m]) {
			printf("No"); return 0;
		}
	}
	for(int x = 1; x <= tot; x++) {
		for(int y : to[x]) if(cir[x] != cir[y]) {
			zto[cir[x]].push_back(cir[y]);
			d[cir[y]]++;
		}
	}
	topo();
	cerr << ok(641991574) << endl;
	int l = 0, r = 1e9, ans = 0;
	while (l <= r) {
		if (ok(l + r >> 1)) {
			ans = r = l + r >> 1;
			r--;
		} else {
			l = (l + r >> 1) + 1;
		}
	}
	printf("Yes\n");
	ok(ans);
	int cnt = 0;
	for(int i = 1; i <= m; i++) if (vis[cir[i]] == 1) {
		cnt++;
	}
	cout << ans << " " << cnt << endl;
	for(int i = 1; i <= m; i++) if (vis[cir[i]] == 1) {
		printf("%d ", i);
	}
}

发布了266 篇原创文章 · 获赞 93 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/jokerwyt/article/details/102948477