bzoj 3495: PA2010 Riddle 2-sat
Description
k個國家,幾個城市,m條邊。
要求每個國家有且僅有一個首都,每條邊兩端的城市至少要有一個首都。
判斷是否有解, 有解輸出“TAK”,無解輸出"NIE"
1 < = k, N ,M , < =1000000。
如果只有第一對關係,兩邊至少有一個首都,那麼很明顯就是“或”的關係了,說明如果左邊點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;
}