• 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),显然这三种是一个 ( ext{2-SAT})

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

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

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

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


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

    不妨让其变成 ( ext{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;
    }
    
  • 相关阅读:
    线程池学习笔记
    线性表的顺序存储和链式存储的实现(C)
    二叉树遍历算法——包含递归前、中、后序和层次,非递归前、中、后序和层次遍历共八种
    【Linux学习笔记】栈与函数调用惯例
    Linux网络编程简单示例
    Linux常用C函数-接口处理篇(网络通信函数)
    android的快速开发框架集合
    Android访问网络数据的几种方式Demo
    Linux C函数库大全
    Mysql 之配置文件my.cnf
  • 原文地址:https://www.cnblogs.com/dmoransky/p/12633593.html
Copyright © 2020-2023  润新知