洛谷3852 BZOJ4945 NOI2017 游戏 2-SAT

题目链接
题意:有A、B、C、三种车,有n局游戏,每局游戏只能用一种车,每局有一种车不能用,也有一些局三种车都能用,但是三种车都能用的不超过8局,还有一些第i局用了某种车第j局就必须用某种车的限制,求一种合法的方案(若无合法方案,则输出-1)。

题解:
首先根据部分分的小提示,我们发现了三种车都可以的地图只会出现不超过8次,而且很多测试点是没有这种地图的。那么我们就先考虑没有这种地图应该怎么做。
我们发现,这道题是每一局都要从3种车中选一种,还有一些限制条件,然后再看到好几万的数据范围,一脸懵逼——3-SAT怎么可能可做?既然我们知道,3-SAT不可做,题目又出出来了,那么我们就应该去想办法把它转化成2-SAT问题。
对2-SAT有一定了解的话,这个转化可能并不难想到。我们发现,因为每一局都有一个限制,那么相当于每局都是二选一,那么我们可以把这两种情况看作2-SAT中的两个对立节点,这两者中必选一个。
然后我们处理那些限制,由于2-SAT的连边是根据“必须”关系来连的,即我们选了 i i i就必须选 i i i连到的所有点,所以用这个性质来处理限制。我们设 h x hx hx为第 x x x局假如选择的车,设 h y hy hy为第 y y y局限制必须选的车。如果第 x x x局本身就不能选 h x hx hx这种车,那么直接跳过这条限制。如果第 x x x局有 h x hx hx这种车可选,但是第 y y y局本身不能选 h y hy hy这种车,那么意味着如果第 x x x局选了 h x hx hx,那么第 y y y局就要选 h y hy hy,但是这样是不可行的,那么我们就必须不选 h x hx hx,而去选它的对立节点,所以这种情况就从 h x hx hx向它的对立节点连边。如果两种本身都在该局可选,那么我们就从 x x x y y y连边,由于2-SAT要用逆否命题保证对称性(我是这么理解的,如果不对敬请斧正),所以我们还有再从 y y y的对立节点向 x x x的对立节点连边即可。这样图就建好了,用2-SAT模板就能求出答案了。
2-SAT输出方案是缩点、判完有解之后建缩后的点的反图,进行拓扑排序,如果点 x x x所在的连通块的拓扑序在 x ′ x' x之后,那么用第一个点,否则用第二个点。但是由于tatrjan的求强连通分量的过程中连通块的编号就是按拓扑排序逆序给出的(不是非常明白),所以只需要比较 x x x x ′ x' x所在的连通块的编号即可完成判断(可以理解为卡了一波常数?)。
update:貌似是有一个结论,就是反图的拓扑序等于正图做tarjan时SCC的编号顺序。但是证明我现在还是不知道。

那么这样,对于没有三种车都可以选的局的情况我们就做完了。接下来就该考虑如何处理三种车可以用的局。
由于我们发现这样的局很少,所以我们考虑暴力处理,由刚才的做法我们得到启发,假如一个局有了一种车是不能用的限制,我们就会做了,那么我们就枚举每局以及依次限制A车、B车、C车,每次建图跑2-SAT,这样我们就得到了一个 O ( 3 8 ∗ ( m + n ) ) O(3^8*(m+n)) O(38(m+n))的做法,(似乎是能过90分),但是还是过不了!其实我们要做的是让这些没有限制的局也从A、B、C中选一个,从而形成合法解,那么其实对于每一个无限制的局,前两次的枚举限制无论是限制不能选哪两种车,都已经提供了3种可选的车。具体点解释就是,假如前两次我们分别现在不能选A车、不能选B车,那么第一次B、C车可以选,第二次A、C车可以选,三种车已经都有了被选择的机会,所以不需要再去枚举第三次了。于是我们就得到了一个 O ( 2 8 ∗ ( m + n ) ) O(2^8*(m+n)) O(28(m+n))的做法,就可以过掉本题了。
思路想出来之后代码里还是有些细节的,写错细节而丢分还是很难受的。这个题我的代码写得很不简练。
PS:感叹两句,好像好久没写博客了,暑假过得真快啊。暑假里果然是颓废,写的题看了看也没什么值得记的。最近学了一下2-SAT,但是感觉理解得并不深刻,所以没有写学习笔记。一些比较简单的2-SAT没有什么值得记的地方,所以就很久没有写博客了。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,d,m,hed[100010],cnt,book[10],ji,u[300010],v[300010],ct;
int dfn[100010],low[100010],vis[100010],sta[100010],tp,kuai[100010],pd,shu,tot,pan;
char s[50010],cur[500010][2];
struct node
{
	int to,next;
}a[1000100];
struct limit
{
	int x,y;
	char hx,hy;
}b[100010];
inline void add(int from,int to)
{
	a[++cnt].to=to;
	a[cnt].next=hed[from];
	hed[from]=cnt;
}
inline void tarjan(int x)
{
	sta[++tp]=x;
	vis[x]=1;
	dfn[x]=low[x]=++shu;
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y])
		low[x]=min(low[x],low[y]);
	}
	if(dfn[x]==low[x])
	{
		++tot;
		while(sta[tp+1]!=x)
		{
			kuai[sta[tp]]=tot;
			vis[sta[tp]]=0;
			--tp;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&d);
	scanf("%s",s+1);
	scanf("%d",&m);
	for(int i=1;i<=m;++i)
	cin>>b[i].x>>b[i].hx>>b[i].y>>b[i].hy;
	for(int i=1;i<=n;++i)
	{
		if(s[i]=='x')
		book[++ji]=i;
	}
	for(int i=0;i<=(1<<d)-1;++i)
	{
		for(int j=0;j<=d-1;++j)
		{
			if((1<<j)&i)
			s[book[j+1]]='a';
			else 
			s[book[j+1]]='b';
 		}
 		for(int j=1;j<=cnt;++j)
 		{
 			a[i].to=0;
 			a[i].next=0;
		}
 		memset(hed,0,sizeof(hed));
 		cnt=0;
 		ct=0;
 		for(int i=1;i<=n;++i)
 		{
 			if(s[i]=='a')
 			{
 				cur[i][0]='B';
 				cur[i][1]='C';
			}
			else if(s[i]=='b')
			{
				cur[i][0]='A';
				cur[i][1]='C';
			}
			else
			{
				cur[i][0]='A';
				cur[i][1]='B';
			}
		}
 		for(int j=1;j<=m;++j)
 		{
 			if(s[b[j].x]+1-'a'==b[j].hx+1-'A')
 			continue;
 			if(s[b[j].y]+1-'a'==b[j].hy+1-'A')
 			{
				if(b[j].hx==cur[b[j].x][0])
				{
					add(b[j].x,b[j].x+n);
 					u[++ct]=b[j].x;
 					v[ct]=b[j].x+n;					
				}
 				else
 				{
 					add(b[j].x+n,b[j].x);
 					u[++ct]=b[j].x+n;
 					v[ct]=b[j].x;	
				}
			}			
 			else
 			{
 				if(b[j].hx==cur[b[j].x][0]&&b[j].hy==cur[b[j].y][0])
 				{
 					add(b[j].x,b[j].y);
 					u[++ct]=b[j].x;
 					v[ct]=b[j].y;
 					add(b[j].y+n,b[j].x+n);//Äæ·ñÃüÌâ¶Ô³Æ 
					u[++ct]=b[j].y+n;
					v[ct]=b[j].x+n;
				}
 				else if(b[j].hx==cur[b[j].x][0]&&b[j].hy==cur[b[j].y][1])
 				{
 					add(b[j].x,b[j].y+n);
 					u[++ct]=b[j].x;
 					v[ct]=b[j].y+n;
 					add(b[j].y,b[j].x+n);//Äæ·ñÃüÌâ¶Ô³Æ 
					u[++ct]=b[j].y;
					v[ct]=b[j].x+n;
				}
				else if(b[j].hx==cur[b[j].x][1]&&b[j].hy==cur[b[j].y][0])
 				{
 					add(b[j].x+n,b[j].y);
 					u[++ct]=b[j].x+n;
 					v[ct]=b[j].y;
 					add(b[j].y+n,b[j].x);//Äæ·ñÃüÌâ¶Ô³Æ 
					u[++ct]=b[j].y+n;
					v[ct]=b[j].x;
				}
				else if(b[j].hx==cur[b[j].x][1]&&b[j].hy==cur[b[j].y][1])
 				{
 					add(b[j].x+n,b[j].y+n);
 					u[++ct]=b[j].x+n;
 					v[ct]=b[j].y+n;
 					add(b[j].y,b[j].x);//Äæ·ñÃüÌâ¶Ô³Æ 
					u[++ct]=b[j].y;
					v[ct]=b[j].x;
				}
			}
		}
		shu=0;
		tot=0;
		memset(kuai,0,sizeof(kuai));
		memset(low,0,sizeof(low));
		memset(dfn,0,sizeof(dfn));
		tp=0;
		for(int j=1;j<=2*n;++j)
		{
			if(!dfn[j])
			tarjan(j);
		}
		pan=0;
		for(int j=1;j<=n;++j)
		{
			if(kuai[j]==kuai[j+n])
			{
				pan=1;
				break;
			}
		}
		if(pan==1)
		continue;
		pd=1;		
		for(int j=1;j<=n;++j)
		{
			if(kuai[j]<kuai[j+n])
			{
				if(s[j]=='a')
				printf("B");
				else
				printf("A");
			}
			else
			{
				if(s[j]=='c')
				printf("B");
				else 
				printf("C");
			}
		}		
		if(pd==1)
		{
			printf("\n");
			break;
		}
	}
	if(pd==0)
	printf("-1\n");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值