NOI2017 游戏

题目链接

Description

\(n\) 次比赛,每次比赛类别是 \(x, a, b, c\)

每次从小 \(L\) 所有的赛车中 \(A, B, C\) 三选一参加比赛。

\(A\) 赛车不能在 \(a\)\(B\) 赛车不能在 \(b\)\(C\) 赛车不能在 \(c\)

还有一些约束条件:
\(i\) 场使用型号 \(x\),在 \(j\) 场必须使用 \(y\)

问是否存在合法解,如果存在随便输出一组方案。

还有一个条件是有 \(d\)\(x\)

Solution

发现 \(a\) 只能匹配 \(B, C\)\(b\) 只能匹配 \(A, C\)\(c\) 只能匹配 \(A, B\),显然这三种是一个 \(\text{2-SAT}\)

考虑 \(d = 0\) 的情况怎么做,即对于约束条件 \(i, x, j, y\),如何处理:

  • \(i\) 不能用 \(x\) 车,那么这个条件莫须有(因为 \(i\) 永远不会选 \(x\) 车),不管了

  • \(j\) 不能用 \(y\) 车,那么 \(i\) 也不能用 \(x\) 车,所以直接让 \(i, x\) 这个条件矛盾即可,即让 \(i\) 连边 \(i'\)

  • 否则即均符合题意要求,那么直接连边,另外还要注意加入逆否命题!


但是 \(x\) 有三种取值,\(\text{3-SAT}\)\(NPC\) 问题没法搞啊...一看数据范围, \(d \le 8\)\(3 ^ 8\) 暴力会爆炸过不了 \(n \le 50000\)。考虑到暴力枚举的方法太绝对了,相当于 \(\text{3-SAT} \Rightarrow \text{1-SAT}\)

不妨让其变成 \(\text{2-SAT}\) 变换一下 \(QAQ\),对于每个 \(x\)

  • 强制不选 \(A\),即只能选 \(BC\)
  • 强制不选 \(B\),即只能选 \(AC\)

考虑到一个合法方案中 \(x\) 的取值是唯一的,所以如果存在解,必然能可以找到解。(尝试当 \(x\) 在合法解中是任意形式,总会在某种情况被枚举到)

所以这样我们每个就枚举两种情况就行了,然后按照 \(d = 0\) 来做...

复杂度

\(O(2 ^ d(n + m))\)

Code

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 50005, M = 100005;

int n, m, d, c[10], tot, head[N << 1], numE;
char s[N];
int A[M], B[M], X[M], Y[M], st[N], top;
int dfn[N << 1], low[N << 1], dfncnt, col[N << 1], cnt;
bool ins[N << 1];

struct E{
	int next, v;
} e[M << 1];

void inline add(int u, int v) {
	e[++numE] = (E) { head[u], v };
	head[u] = numE;
}

void inline clear() {
	numE = top = cnt = dfncnt = 0;
	memset(head, 0, sizeof head);
	memset(dfn, 0, sizeof head);
	memset(ins, false, sizeof ins);
	memset(col, 0, sizeof 0);
}

int inline get(int i, int j) {
	if (s[i] == 'a') return i + n * (j - 1);
	else if (s[i] == 'b') return i + n * (j == 0 ? 0 : 1);
	else return i + n * j;
}

int inline opp(int x) {
	return x <= n ? x + n : x - n;
}

bool inline check() {
	for (int i = 1; i <= n; i++) 
		if (col[i] == col[i + n]) return false;
	return true;
}

int inline change(int i, int j) {
	if (s[i] == 'a') return j + 1;
	else if (s[i] == 'b') return j == 0 ? 0 : 2;
	else return j;
}

void tarjan(int u) {
	dfn[u] = low[u] = ++dfncnt;
	ins[u] = true, st[++top] = u;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].v;
		if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
		else if (ins[v]) low[u] = min(low[u], dfn[v]); 
	} 
	if (low[u] == dfn[u]) {
		int v; ++cnt;
		do {
			v = st[top--], ins[v] = false, col[v] = cnt;
		} while (v != u);
	}
}

bool inline work() {
	clear();
	for (int t = 1; t <= m; t++) {
		int i = A[t], x = X[t], j = B[t], y = Y[t];
		int a = get(i, x), b = get(j, y);
		if (s[i] - 'a' == x) continue;
		else if (s[j] - 'a' == y) add(a, opp(a));
		else add(a, b), add(opp(b), opp(a));
	}
	for (int i = 1; i <= 2 * n; i++)
		if (!dfn[i]) tarjan(i);

	if (check()) {
		for (int i = 1; i <= n; i++)
			putchar('A' + (col[i] < col[i + n] ? change(i, 0) : change(i, 1)));
		return true;
	}
	return false;
}

int main() {
	scanf("%d%d%s%d", &n, &d, s + 1, &m);
	for (int i = 1; i <= n; i++) if (s[i] == 'x') c[tot++] = i;
	for (int i = 1; i <= m; i++) {
		char x[2], y[2];
		scanf("%d%s%d%s", A + i, x, B + i, y);
		X[i] = x[0] - 'A'; Y[i] = y[0] - 'A';
	}
	for (int i = 0; i < (1 << d); i++) {
		for (int j = 0; j < d; j++) s[c[j]] = (i >> j & 1) ? 'b' : 'a';
		if (work()) return 0;
	}
	printf("-1");
	return 0;
}
posted @ 2020-04-04 19:14  DMoRanSky  阅读(247)  评论(0编辑  收藏  举报