bzoj 3495: PA2010 Riddle 2-sat

Description

k个国家,几个城市,m条边。
要求每个国家有且仅有一个首都,每条边两端的城市至少要有一个首都。
判断是否有解, 有解输出“TAK”,无解输出"NIE"
1  < =  k, N ,M , < =1000000。

       先说下输入吧,n,m,k,然后m对关系,最后k行每行第一个数字x表示第i个国家有几个城市,后面x个数字表示这个国家所占有的城市。

       如果只有第一对关系,两边至少有一个首都,那么很明显就是“或”的关系了,说明如果左边点x不选,那么右边点必须选,如果右边点不选,则左边点必须选,所以从x'向y连一条边,y'向x连一条边。

       最难考虑的就是每个国家只能有一个首都,第一反应就是,如果某一个选了,就会有后面的不能选,前面的也不能选的关系,但是边数是n方的,难以接受。

      dang~dang~dang~dang~下面就是一个神奇的东西,前缀优化建图了。

      我们对于每个点,再开两个点,表示第i个点及之前是或否有被选中过,那么对于这样的新点涉及的边数应该会很少,所以我们考虑v一下如何和连边。

       首先我们用x1表示该点被选中,x2表示该点没被选中,x3表示该点的前缀里有被选中过的,x4表示该点的前缀里没有被选中过的,y表示这个城市在输入顺序中的上一个城市,y1-4的定义与x的定义相同。

       首先,要是这个点选了,那么这个点的前缀里肯定有被选中的点了,从x1向x3连一条边。

       其次,要是这个点的前缀里没有被选中的点,那么这个点也一定没有被选,从x4向x2连一条边。

       接着,最简单的传递,如果我上一个点的前缀里有点了,那么我这个点也有,则从y3向x3连一条边,

       然后,与上一个一样,如果我这个点的前缀里没有点,那么它之前也没有,则从x4向y4连一条边

       还有,要是我这个点被选了,说明我上一个点及之前肯定没有被选,则从x3向y4连一条边,

       最后,如果这个点选了,那么它上一个点的前缀里也不会有点,则从x1向y4连一条边,

       最后的最后tarjan缩点判断可行性即可,注意有两种情况,第i个点选或不选同时在一个集合,以及第i个点的前缀有没有选点同在在一个集合,只要满足一种就无可行解了。

       下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stack>
#define maxn 3000005
using namespace std;
//0:选,1:不选,2:存在,3:不存在 
int n,m,k,tot,num,cnt;
int head[maxn<<2],to[6*maxn],nex[6*maxn],pre[6*maxn];
void add(int x,int y)
{
	to[++tot]=y;nex[tot]=head[x];head[x]=tot;
}
int low[maxn<<2],dfn[maxn<<2],bel[maxn<<2];
bool vis[maxn<<2];
stack<int>s;
void tarjan(int now)
{
	low[now]=dfn[now]=++cnt;
	vis[now]=1;
	s.push(now);
	for(int i=head[now];i;i=nex[i])
	{
		if(!dfn[to[i]])
		{
			tarjan(to[i]);
			low[now]=min(low[now],low[to[i]]);
		}
		else if(vis[to[i]])
		{
			low[now]=min(low[now],dfn[to[i]]);
		}
	}
	if(low[now]==dfn[now])
	{
		num++;
		int temp;
		do
		{
			temp=s.top();s.pop();
			bel[temp]=num;
			vis[temp]=0;
		}while(temp!=now);
	}
	return;
}
int main()
{
	memset(pre,-1,sizeof(pre));
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);x--;y--;
		int x1=((x<<2)|1),y1=((y<<2)|1);
		x<<=2;y<<=2;
		add(x1,y);add(y1,x);
	}
	for(int i=1;i<=k;i++)
	{
		int x,last;
		scanf("%d%d",&x,&last);
		last--;
		for(int j=1;j<x;j++)
		{
			int y;
			scanf("%d",&y);y--;
			pre[y]=last;last=y;
		}
	}
	for(int i=0;i<n;i++)
	{
		int x1=(i<<2),x2=(x1|1),x3=(x2+1),x4=(x3+1);
		add(x1,x3);add(x4,x2);
		if(pre[i]!=-1)
		{
			int j=pre[i];
			int y1=(j<<2),y2=(y1|1),y3=(y2+1),y4=(y3+1);
			add(y3,x3);add(x4,y4);add(y3,x2);add(x1,y4);
		}
	} 
	for (int i=0;i<(n<<2);i++)
    if (!dfn[i]) 
	tarjan(i);
    for (int i=0;i<(n<<2);i++)
    if (bel[i]==bel[i^1]) 
	{
		printf("NIE");
		return 0;
	}
	printf("TAK\n");
	return 0;
}#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stack>
#define maxn 3000005
using namespace std;
//0:选,1:不选,2:存在,3:不存在 
int n,m,k,tot,num,cnt;
int head[maxn<<2],to[6*maxn],nex[6*maxn],pre[6*maxn];
void add(int x,int y)
{
	to[++tot]=y;nex[tot]=head[x];head[x]=tot;
}
int low[maxn<<2],dfn[maxn<<2],bel[maxn<<2];
bool vis[maxn<<2];
stack<int>s;
void tarjan(int now)
{
	low[now]=dfn[now]=++cnt;
	vis[now]=1;
	s.push(now);
	for(int i=head[now];i;i=nex[i])
	{
		if(!dfn[to[i]])
		{
			tarjan(to[i]);
			low[now]=min(low[now],low[to[i]]);
		}
		else if(vis[to[i]])
		{
			low[now]=min(low[now],dfn[to[i]]);
		}
	}
	if(low[now]==dfn[now])
	{
		num++;
		int temp;
		do
		{
			temp=s.top();s.pop();
			bel[temp]=num;
			vis[temp]=0;
		}while(temp!=now);
	}
	return;
}
int main()
{
	memset(pre,-1,sizeof(pre));
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);x--;y--;
		int x1=((x<<2)|1),y1=((y<<2)|1);
		x<<=2;y<<=2;
		add(x1,y);add(y1,x);
	}
	for(int i=1;i<=k;i++)
	{
		int x,last;
		scanf("%d%d",&x,&last);
		last--;
		for(int j=1;j<x;j++)
		{
			int y;
			scanf("%d",&y);y--;
			pre[y]=last;last=y;
		}
	}
	for(int i=0;i<n;i++)
	{
		int x1=(i<<2),x2=(x1|1),x3=(x2+1),x4=(x3+1);
		add(x1,x3);add(x4,x2);
		if(pre[i]!=-1)
		{
			int j=pre[i];
			int y1=(j<<2),y2=(y1|1),y3=(y2+1),y4=(y3+1);
			add(y3,x3);add(x4,y4);add(y3,x2);add(x1,y4);
		}
	} 
	for (int i=0;i<(n<<2);i++)
    if (!dfn[i]) 
	tarjan(i);
    for (int i=0;i<(n<<2);i++)
    if (bel[i]==bel[i^1]) 
	{
		printf("NIE");
		return 0;
	}
	printf("TAK\n");
	return 0;
}



        




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值