P3513 [POI 2011] KON-Conspiracy 题解

\(\text{P3513 [POI 2011] KON-Conspiracy 题解}\)

本身并不难,但是有点意思的题。

首先这个人与人之间的限制容易想到用 2-SAT 来做并判掉无解,求出一组可行解 ,但是解的个数是难求的。对于一个 2-SAT 本身我们难以求出解的个数,因此我们需要考虑一下题目里其它的特殊性质。没有明显的入手点,我们不妨以求出的一组可行解出发, 尝试构造所有可能解的个数。注意到原解和可行解之间两个组之间分别只会有至多一个人换到另外一组,否则显然是不合法的,换句话说就是可能的构造方式只有两组之间交换一个人、一组给另外一组一个人这两种方式。

  • 两组之间交换

一种方案是要求合作组给出的点与阴谋组没有边,且阴谋组给出的点和合作组所有点都有边。不过当合作组给出的点恰好只与阴谋组选择的点有一条边时或是阴谋组给出的点只和合作组选择的点没有边时同样合法。

  • 一组给另一组一个

那么要求和上面是类似的,不过只要求没有边/右边即可。

代码:

#include <bits/stdc++.h>
using namespace std;
const int M = 5e3 + 5, N = 1e4 + 5;
int n;
bool mp[M][M];
vector<int>v[N];
int dfn[N], low[N], tim;
int stk[N], top;
bool ins[N];
int bel[N], tot;
void tarjan(int x) {
	dfn[x] = low[x] = ++tim;
	stk[++top] = x;
	ins[x] = 1;
	for (int y : v[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 (low[x] == dfn[x]) {
		int y;
		++tot;
		do {
			y = stk[top--];
			ins[y] = 0;
			bel[y] = tot;
		} while (x != y);
	}
}
#define t(i) (i)
#define f(i) (i + n)
void add(int x, int y) {
	v[x].push_back(y);
}
vector<int>vt, vf;
int num[N], nxt[N];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		int k, j;
		cin >> k;
		sum += k;
		while (k--) {
			cin >> j;
			mp[i][j] = 1;
		}
	}
	if (sum) sum = 0;
	else sum = -1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) {
			if (i == j) continue;
			if (mp[i][j]) add(f(i), t(j));
			else add(t(i), f(j));
		}
	for (int i = 1; i <= n * 2; i++)
		if (!dfn[i]) tarjan(i);
	for (int i = 1; i <= n; i++) {
		if (bel[t(i)] == bel[f(i)]) cout << "0\n", exit(0);
		if (bel[t(i)] < bel[f(i)]) vt.push_back(i);
		else vf.push_back(i);
	}
	int lt = vt.size(), lf = vf.size();
	int ans = (lt && lf), nt = 0, nf = 0;
	for (int i : vt)
		for (int j : vf) {
			if (mp[i][j]) num[i]++, nxt[i] = j;
			else num[j]++, nxt[j] = i;
		}
	for (int i : vt) {
		if (num[i] == 0) ++nt;
		if (num[i] == 1 && num[nxt[i]] == 0) ++ans;
	}
	for (int i : vf) {
		if (num[i] == 0) ++nf;
		if (num[i] == 1 && num[nxt[i]] == 0) ++ans;
	}
	cout << ans + nt * nf + nt * (lt > 0) + nf * (lf > 0) + sum << '\n';
	return 0;
}
posted @ 2025-05-03 12:01  长安19路  阅读(33)  评论(0)    收藏  举报