P6378 [PA 2010] Riddle 题解

P6378 [PA 2010] Riddle

nnn 个点 mmm 条边的无向图被分成 kkk 个部分。每个部分包含一些点。

请选择一些关键点,使得每个部分有一个关键点,且每条边至少有一个端点是关键点。判断是否有解。


连边是容易的。

因为每个部分都正好有 111 个点,所以设点集为 a1,a2,...,an{a_1,a_2,...,a_n}a1,a2,...,an,则连 ∀1≤i,j(i,j are not the same)≤n,ai→−aj\forall 1 \le i,j(i , j \ \text{are not the same}) \le n,a_i \to -a_j∀1i,j(i,j are not the same)n,aiaj 的边。

对于每一条边 (x,y)(x,y)(x,y),有 −x→y-x \to yxy−y→x-y \to xyx

然后就可以幸福愉快地跑 2-SAT。复杂度达到了残酷的 O(n2)O(n^2)O(n2),无法通过。


因为只有一个部分中的点连边花销较大,边的数量实际上还是 nnn 的量级。所以考虑优化一下同一个部分中的点的连边。

这样是我们最开始的连边方式。

但是这样还是太慢了。

这个时候不能删边也不能删点,考虑类似一开始 2-SAT 的方式加点。

我们考虑再建 2×n2 \times n2×n 个虚点。

对于上面的点,每一个点连向自己的虚点。

对于下面的点,每一个自己的虚点连向自己。

感觉还更乱了。

然后再对于上面的点,每一条出边的起点再设为它的虚点。

然后再对于下面的点,每一条入边的终点再设为它的虚点。

这样就可以开始幸福愉快地删边了!

经过尝试发现这样是合法的,也是可以的。

这样,边数又变成了 O(n)O(n)O(n)

点数注意开 4 倍空间!如果你采用的是链式前向星,也要注意空间(还要开 6 倍)。

#include <bits/stdc++.h>
using namespace std;
const int N = 4000010;
int n, m, k;
vector<int> v[N], p;
int dfn[N], low[N], ind;
stack<int> stk;
int color[N], s_cnt;

void tarjan(int u) {
	dfn[u] = low[u] = ++ind;
	stk.push(u);
	for (auto i : v[u]) {
		if (!dfn[i])
			tarjan(i), low[u] = min(low[u], low[i]);
		else if (!color[i])
			low[u] = min(low[u], dfn[i]);
	}
	if (low[u] == dfn[u]) {
		int x;
		s_cnt++;
		do {
			x = stk.top();
			stk.pop();
			color[x] = s_cnt;
		} while (x != u);
	}
}//tarjan

int main() {
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++) {
		int x, y;
		cin >> x >> y;
		v[x + n].push_back(y);
		v[y + n].push_back(x);
	}
	for (int i = 1; i <= k; i++) {
		int x;
		cin >> x;
		p.clear();
		while (x--) {
			int a;
			cin >> a;
			p.push_back(a);
		}
		for (auto a : p)
			v[a].push_back(a + 2 * n), v[a + 3 * n].push_back(a + n);
		for (int i = 0; i < (int)p.size(); i++) {
			if (i != 0)
				v[p[i]].push_back(p[i - 1] + 3 * n), v[p[i] + 3 * n].push_back(p[i - 1] + 3 * n);
			if (i != p.size() - 1)
				v[p[i] + 2 * n].push_back(p[i + 1] + n), v[p[i] + 2 * n].push_back(p[i + 1] + 2 * n);
		}//连边,上面讲过。一共有 6 种边,一种都不要忘记
	}
	for (int i = 1; i <= 4 * n; i++)
		if (!dfn[i])
			tarjan(i);
	for (int i = 1; i <= n; i++)
		if (color[i] == color[i + n]) {//直接判断无解即可
			cout << "NIE\n";
			return 0;
		}
	cout << "TAK\n";
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值