[NOI2017]游戏(2-SAT+枚举)

题目

解法

2-SAT板子题

如果没有x这个字符的话,问题变成每个点都有两种选法,有一些限制,显然是个2-SAT问题,设拆出来的两个点分别为\(i\)\(i'\),则对于限制\(x->y\),连边\((x,y),(y',x')\),跑tarjan即可(2-SAT基操嘛。)

即使现在有了x这个字符,我们也可以假装一个点仍只有两种选法,枚举这两种选法,并且确保\(A,B,C\)至少在一种选法中存在就行了;具体来说,对于每个x,枚举\(A\)\(B\)中哪一个不能选就足以表示出所有情况了

一句话就是:枚举将每个x变成a或者b,对每种情况跑最简单的\(2-SAT\)就行了

输出方案的话比较\(color[i]\)\(color[i+n]\)即可,具体见输出部分

时间复杂度\(O(n*2^n)\)

Code

//因为spacial judge,最后一行不要输出回车
#include<bits/stdc++.h>
#define N 100005
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
using namespace std;
int n,m,d,ref[N][3],rev[N][3];
int dfn[N],low[N],c,col,color[N];
int st[N],top;
int ban[10];
char lim[N];
struct R { int x,dx,y,dy; } r[N];
struct Edge
{
	int next,to;
}edge[N<<3];int head[N],cnt;
void add_edge(int from,int to)
{
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	head[from]=cnt;
}
//0:A   1:B    2:C

template <class T>
void read(T &x)
{
	char c;int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign;
}
void in()
{
	read(n);read(d);
	scanf("%s",lim);
	read(m);
	for(int i=1;i<=m;++i)
	{
		char op[2];
		read(r[i].x); scanf("%s",op); r[i].dx=op[0]-'A'; 
		read(r[i].y); scanf("%s",op); r[i].dy=op[0]-'A';
	}
}
void tarjan(int rt)
{
	dfn[rt]=low[rt]=++c;
	st[++top]=rt;
	for(int i=head[rt];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(!dfn[v])
		{
			tarjan(v);
			low[rt]=Min(low[rt],low[v]);
		}
		else if(!color[v]) low[rt]=Min(low[rt],dfn[v]);
	}
	if(dfn[rt]==low[rt])
	{
		color[rt]=++col;
		while(st[top]!=rt) color[st[top--]]=col;
		--top;
	}
}
bool work()
{
	memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(ref,0,sizeof(ref));
	memset(rev,0,sizeof(rev));
	memset(color,0,sizeof(color));
	cnt=top=c=col=0;
	int ct=0;
	for(int i=1;i<=n;++i)
	{	
		int no,one,two;
		if(lim[i-1]=='a') no=0,one=1,two=2;
		else if(lim[i-1]=='b') no=1,one=0,two=2;
		else if(lim[i-1]=='c') no=2,one=0,two=1;
		else
		{
			++ct;
			if(ban[ct]) no=0,one=1,two=2;
			else no=1,one=0,two=2;
		}
		ref[i][no]=0,ref[i][one]=1,ref[i][two]=2;
		rev[i][1]=one,rev[i][2]=two;
	}
	for(int i=1;i<=m;++i)
	{
		int x=r[i].x,dx=r[i].dx,y=r[i].y,dy=r[i].dy;
		if(!ref[x][dx]) continue;//不能用这个车
		else if(!ref[y][dy]) add_edge(x+(ref[x][dx]-1)*n , x+(ref[x][dx]-1 ? 0:n));//一用就死
		else
		{
			add_edge(x+(ref[x][dx]-1)*n , y+(ref[y][dy]-1)*n);
			add_edge(y+(ref[y][dy]-1?0:n) , x+(ref[x][dx]-1?0:n));//感觉这样子表示1,2节点好蠢。。。 
		}
	}
	for(int i=1,t=(n<<1);i<=t;++i) if(!dfn[i]) tarjan(i);
	bool ret=1;
	for(int i=1;i<=n;++i) if(color[i]==color[i+n]) ret=0;
	return ret;
}
int main()
{
	in();
	bool ok=0;
	for(int i=0,t=(1<<d);i<t;++i)
	{
		for(int j=0;j<d;++j) ban[j+1]=(i>>j&1);
		if(work())
		{
			for(int i=1;i<=n;++i) printf("%c",color[i]<color[i+n] ? rev[i][1]+'A' : rev[i][2]+'A');
			ok=1;
			break;
		}
	}
	if(!ok) printf("-1");
	return 0;
}
posted @ 2019-10-12 11:46  擅长平地摔的艾拉酱  阅读(111)  评论(0编辑  收藏  举报
/*取消选中*/