zoukankan      html  css  js  c++  java
  • [NOI2017]游戏「2-SAT」

    [NOI2017]游戏「2-SAT」

    题目描述

    小 L 计划进行 (n) 场游戏,每场游戏使用一张地图,小 L 会选择一辆车在该地图上完成游戏。

    小 L 的赛车有三辆,分别用大写字母 (A)(B)(C) 表示。地图一共有四种,分别用小写字母 (x)(a)(b)(c) 表示。

    其中,赛车 (A) 不适合在地图 (a) 上使用,赛车 (B) 不适合在地图 (b) 上使用,赛车 (C) 不适合在地图 (c) 上使用,而地图 (x) 则适合所有赛车参加。

    适合所有赛车参加的地图并不多见,最多只会有 (d) 张。

    (n) 场游戏的地图可以用一个小写字母组成的字符串描述。例如:(S= exttt{xaabxcbc}) 表示小L计划进行 (8) 场游戏,其中第 (1) 场和第 (5) 场的地图类型是 (x),适合所有赛车,第 (2) 场和第 (3) 场的地图是 (a),不适合赛车 (A),第 (4) 场和第 (7) 场的地图是 (b),不适合赛车 (B),第 (6) 场和第 (8) 场的地图是 (c),不适合赛车 (C)

    小 L 对游戏有一些特殊的要求,这些要求可以用四元组 ((i, h_i, j, h_j)) 来描述,表示若在第 $i4 场使用型号为 (h_i)​ 的车子,则第 (j) 场游戏要使用型号为 (h_j)​ 的车子。

    你能帮小 L 选择每场游戏使用的赛车吗?如果有多种方案,输出任意一种方案。

    如果无解,输出 -1

    输入格式

    输入第一行包含两个非负整数 (n), (d)

    输入第二行为一个字符串 (S)

    (n), (d), (S) 的含义见题目描述,其中 (S) 包含 (n) 个字符,且其中恰好 (d) 个为小写字母 (x)

    输入第三行为一个正整数 (m) ,表示有 (m) 条用车规则。

    接下来 (m) 行,每行包含一个四元组 (i,h_i,j,h_j) ,其中 (i,j) 为整数,(h_i,h_j)​ 为字符 (A)(B)(C),含义见题目描述。

    输出格式

    输出一行。

    若无解输出 -1

    若有解,则包含一个长度为 (n) 的仅包含大写字母 (A)(B)(C) 的字符串,表示小 L 在这 (n) 场游戏中如何安排赛车的使用。如果存在多组解,输出其中任意一组即可。

    输入输出样例

    输入 #1

    3 1
    xcc
    1
    1 A 2 B
    

    输出 #1

    ABA
    

    思路分析

    来,今天我们学习(2-SAT),首先从这个黑题入手

    • 每个地图都有两个或三个状态可选,另加了一些限制,显然是一道 (2—SAT) 的题
    • 关键在于对于 (x) 地图,有三种状态,难不成弄个 (3—SAT) ?然而这个做法已被证明是 (NPC)
    • 为了变成 (2-SAT) 问题,我们将 $x $地图分类讨论,第一种是不能选 (A) ,第二种不能选 (B),这时候选 (A)(B)(C) 的情况就都已经囊括了,不须再进一步枚举
    • 接下来是连边,对于每个四元组 (i,h_i,j,h_j)
      • (h_i) 是不可选的,不需连边,跳过即可
      • (h_j) 是不可选的,则需要建边强制i不能选 (h_i)
      • 都可选,直接连边,表示同时选,另外再根据逆否命题建一个反向边,即同时不选
    • 对于方案输出,输出拓扑序大的,即缩点编号小的即可
      细节真的超多

    Code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define N 500100
    using namespace std;
    inline int read(){
    	int x = 0,f = 1;
    	char ch = getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return x*f;
    }
    int n,d,m,dfs_clock,scc_cnt,dfn[N],low[N],head[N],sta[N],top,belong[N],in[N],a[N],b[N],gn[N];
    char s[N],h1[N],h2[N],ans[N];
    struct edge{
    	int to,next;
    }e[N<<2];
    int len;
    void addedge(int u,int v){
    	e[++len].to = v;
    	e[len].next = head[u];
    	head[u] = len;
    }
    void Init(){ //每次改变x图的状态都要重新建图
    	memset(head,0,sizeof(head));
    	memset(dfn,0,sizeof(dfn));
    	memset(low,0,sizeof(low));
    	memset(belong,0,sizeof(belong));
    	memset(in,0,sizeof(in));
    	memset(sta,0,sizeof(sta));
    	dfs_clock = scc_cnt = top  = len = 0;
    }
    void Tarjan(int u){ //Tarjan缩点
    	dfn[u] = low[u] = ++dfs_clock;
    	sta[++top] = u;
    	in[u] = 1;
    	for(int i = head[u];i;i=e[i].next){
    		int v = e[i].to;
    		if(!dfn[v]){
    			Tarjan(v);
    			low[u] = min(low[u],low[v]);
    		}
    		else if(!belong[v])low[u] = min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u]){
    		scc_cnt++;
    		while(1){
    			int x = sta[top--];
    			belong[x] = scc_cnt;
    			in[x] = 0;
    			if(u==x)break;
    		}
    	}
    }
    bool check(){//寻找可行方案
    	for(int i = 1;i <= (n<<1);i++)if(!dfn[i])Tarjan(i);
    	for(int i = 1;i <= n;i++){
    		if(belong[i]==belong[i+n])return false; //两个状态出现在了一个scc里,就没有合理方案
    		if(belong[i]<belong[i+n])ans[i] = (s[i]=='A') ? 'B' : 'A'; //根据字典序优先,第一个状态只能是A或C
    		else ans[i] = (s[i]=='C') ? 'B' : 'C'; //同理
    	}
    	for(int i = 1;i <= n;i++)putchar(ans[i]); //找到一种方案输出就行了
    	return true;
    }
    int main(){
    	n = read(),d = read();
    	d = 0;
    	scanf("%s",s+1);
    	m = read();
    	for(int i = 1;i <= n;i++)if((s[i]-=32)&&s[i]=='X')gn[++d] = i; //x图存储起来
    	for(int i = 1;i <= m;i++){
    		a[i] = read();scanf(" %c",&h1[i]);
    		b[i] = read();scanf(" %c",&h2[i]);
    	}
    	bool flag = 0;
    	for(int i = 0;i <= (1<<d)-1;i++){ //枚举2^d个状态,这里用了2进制位,每个x图相应的2进制位为1就不能A,否则不能选B
    		Init();
    		for(int j = 1;j <= d;j++)s[gn[j]] = (i&(1<<(j-1))) ? 'A' : 'B';
    		for(int j = 1;j <= m;j++){
    			if(h1[j] == s[a[j]])continue;
    			if(h2[j] == s[b[j]]){
    				if(h1[j]=='C' || (h1[j] =='B'&&s[a[j]]=='C'))addedge(a[j]+n,a[j]); //这里的if是根据字典序大小而进行连边操作
    				else addedge(a[j],a[j]+n);
    				continue;
    			}
    			int add1,add2;
    			if(h1[j] == 'C' || (h1[j] == 'B' && s[a[j]] == 'C')) add1 = n;
                else add1 = 0;
                if(h2[j] == 'C' || (h2[j] == 'B' && s[b[j]] == 'C')) add2 = n;
                else add2 = 0;
                addedge(a[j] + add1, b[j] + add2);
                addedge(b[j] - add2 + n, a[j] - add1 + n); //反向边,巧妙处理
            }
            if(check()){flag = 1;break;}
        }
        if(!flag)printf("-1");
        return 0;
    }
    
  • 相关阅读:
    maven命令运行项目
    关于IDEA2020.1版本会出现maven依赖问题
    记录三十天 2020.5.7 day30
    GO
    有关IDEA的Settings常用设置(后续补充....)
    Windows下载安装 破解 idea2019.1.4
    关于tomcat9的startup.bat闪退问题&乱码
    python基础学习(十四)
    python基础学习(十三)
    IDEA常备插件推荐
  • 原文地址:https://www.cnblogs.com/hhhhalo/p/13456413.html
Copyright © 2011-2022 走看看