把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3825】[NOI2017] 游戏(暴搜+2-SAT)

点此看题面

  • \(n\)张地图和"A"、"B"、"C"三种型号的车子。
  • \(i\)张地图有一个字母"a"/"b"/"c"表示这张图上无法开"A"/"B"/"C"型的车,或一个字母"x"表示可以开任意型的车(共有\(d\)个"x")。
  • 给出\(m\)个条件,表示如果第\(x\)张地图上开\(p\)型车就必须在第\(y\)张地图上开\(q\)型车。
  • \(n\le5\times10^4,m\le10^5,d\le8\)

\(3-SAT\)的假象

有三种型号的车,乍一看似乎是\(3-SAT\)问题?

但我们知道,\(3-SAT\)问题是没有多项式解法的,而\(n\le5\times10^4\),肯定暗藏玄机。

于是就发现,\(d\le 8\),除了这\(d\)个"x"之外剩下的不过是\(2-SAT\)问题。

而这\(d\)个"x"可以直接暴搜转化为\(2-SAT\)

一开始智障了以为每个"x"有三种选择,后来发现我们并不需要直接确定"x"的图究竟选什么车。

只要让"x"变成"a"或"b",那么第一种情况下可以选"B"或"C",第二种情况下可以选"A"或"C",实际上已经把三种选择都包括进来了,而问题也同样可以被转化成\(2-SAT\)模型。

转化之后就成了一道\(2-SAT\)大裸题。

众所周知要构造一组合法解,只要对于每个位置选择它拆成的两个点中强连通分量编号较小的那个就好了。

具体建图时要忽略掉这一位上不能选择的字符,可能有些细节。

如果选择\(x\)\(p\)型车会指向\(y\)无法填的\(q\)型车,就从\(x\)\(p\)型车向它可以选的另一种型号的车连边,表示无法选择\(p\)型车。

代码:\(O(2^dm)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define M 100000
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m,d,o[N+5],ex[M+5],ey[M+5];char s[N+5],ep[M+5],eq[M+5];
I bool ID(CI id,char x) {return (x-'A')-(x-'A'>s[id]-'a');}//把对于第id张图的字符转化为0/1
I char CH(CI id,RI x) {return 'A'+x+(x>=s[id]-'a');}//把对于第id张图的0/1转化回字符
namespace G//2-SAT
{
	int ee,lnk[2*N+5],d,dfn[2*N+5],low[2*N+5],T,S[2*N+5],IS[2*N+5],c,bl[2*N+5];struct edge {int to,nxt;}e[2*M+5];
	I void Cl() {ee=d=c=0;for(RI i=1;i<=2*n;++i) lnk[i]=dfn[i]=0;}//清空图
	I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
	I void Tarjan(CI x)
	{
		dfn[x]=low[x]=++d,IS[S[++T]=x]=1;for(RI i=lnk[x];i;i=e[i].nxt) dfn[e[i].to]
			?IS[e[i].to]&&Gmin(low[x],dfn[e[i].to]):(Tarjan(e[i].to),Gmin(low[x],low[e[i].to]));
		if(dfn[x]==low[x]) {++c;W(bl[S[T]]=c,IS[S[T]]=0,S[T--]^x);}
	}
	char res[N+5];I void TwoSAT()
	{
		RI i;for(i=1;i<=2*n;++i) !dfn[i]&&(Tarjan(i),0);//Tarjan缩点
		for(i=1;i<=n;++i) if(bl[i]==bl[i+n]) return;else res[i]=CH(i,bl[i]>bl[i+n]);puts(res+1),exit(0);//同连通块则无解,否则选编号较小的
	}
}
I void Build()//建图+2-SAT验证
{
	G::Cl();for(RI i=1;i<=m;++i) if((ep[i]-'A')^(s[ex[i]]-'a'))//如果x可以选择p
	{
		if(eq[i]-'A'==s[ey[i]]-'a') {G::Add(ex[i]+n*ID(ex[i],ep[i]),ex[i]+n*!ID(ex[i],ep[i]));continue;}//如果y无法选择q
		G::Add(ex[i]+n*ID(ex[i],ep[i]),ey[i]+n*ID(ey[i],eq[i]));//连边
		G::Add(ey[i]+n*!ID(ey[i],eq[i]),ex[i]+n*!ID(ex[i],ep[i]));//连反边
	}G::TwoSAT();
}
I void dfs(CI x) {if(x>d) return Build();s[o[x]]='a',dfs(x+1),s[o[x]]='b',dfs(x+1);}//暴搜每个"x"的两种填法
int main()
{
	RI i;for(scanf("%d%d%s%d",&n,&d,s+1,&m),i=1;i<=m;++i) scanf("%d %c %d %c",ex+i,ep+i,ey+i,eq+i);
	RI t=0;for(i=1;i<=n;++i) s[i]=='x'&&(o[++t]=i);return dfs(1),puts("-1"),0;
}
posted @ 2021-04-08 15:02  TheLostWeak  阅读(47)  评论(0编辑  收藏  举报