← 全部文章

CF587D Duff in Mafia 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF587D Duff in Mafia

题意

  • 给定一张 nn 个点 mm 条边的无向图,每条边有一个颜色 cc 和权值 tt。
  • 你要选出一些边,使得它们是一个匹配,同时剩下的边每种颜色也是一个匹配。
  • 同时,你要最小化选出的边的最大权值。
  • n,m≤5×104n,m \le 5 \times 10^4。

题解

首先二分答案,若此时二分的值为 oo,则所有 >o> o 的边都不能选。

每条边有选和不选两种状态,所以考虑 2-SAT。设第 ii 条边的状态为 xix_i 表示选择它,xi′x^{\prime}_i 表示不选。连边如下:

  1. 对于一定不能选的边,连边 xi→xi′x_i \to x^{\prime}_ i。
  2. 对于选出的边一定要是一个匹配,考虑一个点 pp 上的所有边 x1…kx_{1\dots k},连边 xi→xj′(i≠j)x_i \to x^{\prime}_ j(i \ne j)。
  3. 对于剩下的边每种颜色也一定要是一个匹配,考虑一个点 pp 上颜色相同的所有边 x1…kx_{1\dots k},连边 xi′→xj(i≠j)x^{\prime}_ i \to x_j(i \ne j)。

但这样做第 2,32,3 类边的边数为 O(m2)\mathcal O(m^2),注意到 2-SAT 有一个经典的建图优化——前缀优化。

先考虑第 22 类边,设 x1…kx_{1\dots k} 的前缀点为 s1…ks_{1\dots k},x1…k′x^{\prime}_ {1\dots k} 的后缀点为 s1…k′s^{\prime}_ {1\dots k}。连边如下:

  1. xi→six_i \to s_i,si′→xi′s^{\prime}_ i \to x^{\prime}_ i。
  2. si−1→sis_{i-1} \to s_i,si′→si−1′s^{\prime}_ i \to s^{\prime}_ {i-1}。
  3. si−1→xi′s_{i-1} \to x^{\prime}_ i,xi→si−1′x_i \to s^{\prime}_ {i-1}。

CF587D Duff in Mafia 题解:原文插图

容易发现和第 22 类边是等效的。

而第 33 类边,就把第 22 类边的箭头全部反过来即可。

然后 tarjan 就好了。

总时间复杂度 O((n+m)log⁡w)\mathcal O((n + m) \log w)。

代码

const int N = 5e4 + 7, M = 10 * N;
int n, m, tot, l, r;
int dfn[M], low[M], num, stk[M], top, ins[M], scc[M], cnt;
vi v[N], s, e[M], ans;
struct P {
	int u, v, c, t;
	inline void in() { 	rd(u), rd(v), rd(c), rd(t), r = max(r, t); }
} p[N];

inline void work1() {
	int S, T;
	for (ui i = 0; i < s.size(); i++) {
		int x = ::s[i], y = ::s[i] + m, s = ++tot, t = ++tot;
		e[x].pb(s), e[t].pb(y);
		if (i) e[S].pb(s), e[t].pb(T), e[S].pb(y), e[x].pb(T);
		S = s, T = t;
	}
	s.clear();
}

inline void work2() {
	int S, T;
	for (ui i = 0; i < s.size(); i++) {
		int x = ::s[i], y = ::s[i] + m, s = ++tot, t = ++tot;
		e[s].pb(x), e[y].pb(t);
		if (i) e[s].pb(S), e[T].pb(t), e[y].pb(S), e[T].pb(x);
		S = s, T = t;
	}
	s.clear();
}

void tarjan(int x) {
	dfn[x] = low[x] = ++num, stk[++top] = x, ins[x] = 1;
	for (auto y : e[x])
		if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
		else if (ins[y]) low[x] = min(low[x], dfn[y]);
	if (dfn[x] == low[x]) {
		ins[x] = 0, scc[x] = ++cnt;
		while (x != stk[top--]) {
			int y = stk[top+1];
			ins[y] = 0, scc[y] = cnt;
		}
	}
}

inline void init(int o) {
	for (int i = 1; i <= m; i++) if (p[i].t > o) e[i].pop_back();
}

inline bool pd(int o) {
	for (int i = 1; i <= m; i++) if (p[i].t > o) e[i].pb(i + m);
	for (int i = 1; i <= tot; i++) dfn[i] = 0;
	cnt = num = 0;
	for (int i = 1; i <= tot; i++) if (!dfn[i]) tarjan(i);
	for (int i = 1; i <= m; i++)
		if (scc[i] == scc[i+m]) return init(o), 0;
	return init(o), 1;
}

int main() {
	rd(n), rd(m), tot = m << 1;
	for (int i = 1; i <= m; i++)
		p[i].in(), v[p[i].u].pb(i), v[p[i].v].pb(i);
	for (int i = 1; i <= n; i++) {
		sort(v[i].begin(), v[i].end(),
			[&](int i, int j) { return p[i].c < p[j].c; });
		s = v[i], work1();
		for (ui l = 0, r = 0; l < v[i].size(); l = ++r) {
			s.pb(v[i][l]);
			while (r + 1 < v[i].size()
				&& p[v[i][r+1]].c == p[v[i][r]].c) s.pb(v[i][++r]);
			work2();
		}
	}
	if (!pd(r)) return prints("No"), 0;
	prints("Yes");
	while (l < r) {
		int mid = (l + r) >> 1;
		if (pd(mid)) r = mid;
		else l = mid + 1;
	}
	pd(l);
	for (int i = 1; i <= m; i++) if (scc[i] < scc[i+m]) ans.pb(i);
	print(l, ' '), print(ans.size());
	for (auto x : ans) print(x, ' ');
	return 0;
}

标签:2-SAT、Tarjan、二分、前缀和/差分

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…