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;
}