bzoj 3495: PA2010 Riddle 2-sat

来源:互联网 发布:汉森机器人 知乎 编辑:程序博客网 时间:2024/06/05 06:02

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 3000005using 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 3000005using 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;}