NOI 2017 游戏 题解

题目传送门

题目大意: n n n 次游戏,每次游戏地图为 x , a , b , c x,a,b,c x,a,b,c 四种中的一种,保证 x x x 只出现 d d d 次,有 A , B , C A,B,C A,B,C 三种赛车, A A A 赛车在地图 a a a 上不能用, B , C B,C B,C b , c b,c b,c 同理,以及有 k k k 个限制,每个形如 x , c 1 , y , c 2 x,c1,y,c2 x,c1,y,c2,表示当第 x x x 场比赛用 c 1 c1 c1 赛车时,第 y y y 场比赛一定要用 c 2 c2 c2 赛车,给出一种赛车使用方案。

题解

看到那 k k k 个限制,大概就能想到用 2 − S A T 2-SAT 2SAT,先不考虑 x x x 类地图,那么每次比赛能用的汽车就只有两种,对应 0 / 1 0/1 0/1 两种状态,对于每个限制,假设 c 1 c1 c1 对应状态 X X X c 2 c2 c2 对应状态 Y Y Y,以及 X X X 和状态 X ′ X' X 相对(即一个是 0 0 0,一个是 1 1 1), Y Y Y Y ′ Y' Y 相对,那么让 X X X Y Y Y 连边, Y ′ Y' Y X ′ X' X 连边。

还要考虑第 y y y 次比赛不能用赛车 c 2 c2 c2 的情况,那么此时第 x x x 次比赛也不能用 c 1 c1 c1,那么让 X X X X ′ X' X 连边。

然后考虑地图 x x x,由于数量不超过 8 8 8,很容易想到可以 3 8 3^8 38 枚举,但是还是容易超时。可以发现,当 x x x 枚举为 a a a b b b 后,就已经考虑了使用赛车 A , B , C A,B,C A,B,C 的三种情况,所以就不用枚举到 c c c 了,这样时间复杂度就只有 O ( 2 8 n ) O(2^8n) O(28n)

具体连完边后的求解部分,用 t a r j a n tarjan tarjan 判断一下可行性,然后输出最先遍历到的点即可,也就是判断一下每场比赛的两个状态谁先被遍历到就输出谁。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 200010

int n,d,m;
char s[maxn];
struct Limit{int x,y,c1,c2;}limit[maxn];
int X[10],tot=0;
int type[maxn],A[maxn],B[maxn];//type表示地图种类,A,B表示0/1两种状态对应的点的编号
int work1(int x){return type[x]-1<0?2:type[x]-1;}
int work2(int x){return type[x]+1>2?0:type[x]+1;}
struct edge{int y,next;};
edge e[maxn<<1];
int first[maxn],len;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
int id,dfn[maxn],low[maxn],zhan[maxn],t=0,belong[maxn],cnt;
bool v[maxn];
void tarjan(int x)
{
	dfn[x]=low[x]=++id;zhan[++t]=x;v[x]=true;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(!dfn[y])tarjan(y),low[x]=min(low[x],low[y]);
		else if(v[y])low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		cnt++;int xx;
		do{
			xx=zhan[t--];
			v[xx]=false;
			belong[xx]=cnt;
		}while(xx!=x);
	}
}
void work()
{
	len=cnt=id=0;
	memset(first,0,sizeof(first));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(belong,0,sizeof(belong));
	for(int i=1;i<=m;i++)
	{
		int x=limit[i].x,y=limit[i].y,c1=limit[i].c1,c2=limit[i].c2;
		if(c1==type[x])continue;
		if(c2==type[y])
		{
			if(work1(x)==c1)buildroad(A[x],B[x]);
			else buildroad(B[x],A[x]);
		}
		else
		{
			buildroad(work1(x)==c1?A[x]:B[x],work1(y)==c2?A[y]:B[y]);
			buildroad(work2(y)==c2?A[y]:B[y],work2(x)==c1?A[x]:B[x]);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[A[i]])tarjan(A[i]);
		if(!dfn[B[i]])tarjan(B[i]);
	}
	for(int i=1;i<=n;i++)if(belong[A[i]]==belong[B[i]])return;
	for(int i=1;i<=n;i++)
	if(belong[A[i]]<belong[B[i]])printf("%c",work1(i)+'A');
	else printf("%c",work2(i)+'A');
	exit(0);
}
void dfs(int x)
{
	if(x>d)return (void)work();int p=X[x];
	type[p]=0;A[p]=p+n*work1(p);B[p]=p+n*work2(p);dfs(x+1);
	type[p]=1;A[p]=p+n*work1(p);B[p]=p+n*work2(p);dfs(x+1);
}

int main()
{
	scanf("%d %d %s %d",&n,&d,s+1,&m);
	for(int i=1;i<=n;i++)
	if(s[i]=='x')X[++tot]=i;
	else type[i]=s[i]-'a',A[i]=i+n*work1(i),B[i]=i+n*work2(i);
	for(int i=1;i<=m;i++)
	scanf("%d %s",&limit[i].x,s+1),limit[i].c1=s[1]-'A',
	scanf("%d %s",&limit[i].y,s+1),limit[i].c2=s[1]-'A';
	dfs(1);
	printf("-1");
}

顺便附上一个判断答案是否合法的程序(先输入你的答案,然后换行把输入复制进去,nice~表示没问题,f**k表示不合法。

#include <cstdio>
#define maxn 200010
#define boom return printf("f**k"),0

char s[maxn],ss[maxn];
int n,m;

int main()
{
	scanf("%s %d %d %s",s+1,&n,&m,ss+1);
	for(int i=1;i<=n;i++)if(s[i]-'a'+'A'==ss[i])boom;
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;char xx,yy;
		scanf("%d %s",&x,ss+1);xx=ss[1];
		scanf("%d %s",&y,ss+1);yy=ss[1];
		if(s[x]==xx&&s[y]!=yy)boom;
	}
	printf("nice~");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值