bzoj3495 PA2010 Riddle
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=3495
题意:
k个国家,几个城市,m条边。
要求每个国家有且仅有一个首都,每条边两端的城市至少要有一个首都。
判断是否有解, 有解输出“TAK”,无解输出”NIE”。
数据范围
1 < = k, N ,M , < =1000000
题解:
传统建边O(n^2),舍弃,考虑前缀优化。
解法是前缀一个点拆成四个点,分别表示:这个城市是首都/这个城市不是首都、这个城市所属国家中到这个城市的前缀包含首都\不包含首都。
令城市点为i,对应前缀点pre[i],上面加个 ` 表示反点。这里的建边都表示同时建了反变(
u−>v,v′−>u′
)
那么
i′−>j
i−>pre[i]
i−>pre[i−1]‘
pre[i]−>pre[i−1]
跑2-SAT即可。
这道题让我想到了UOJ 210
很容易想到前后缀建边优化,可以从这类题中归纳出一个建边方向的问题 。
UOJ210的前后缀可以直接与单独的点相关,因为前缀/后缀假话点可以直接指向这个人是犯人,而这个人是犯人却不能直接指向某条供词是假的。
此题,前后缀城市有首都 也不能对应到某个城市是不是首都 ,因此是点的真连向前缀的真。
但是网上很多题解说只需要前缀即可,为什么?
实际上,只建前缀点并不能保证有且只有一个首都,可能合法方案是一个国家根本没有首都。所有的前缀都是假的。
那么建立后缀节点呢?前缀全假总能对应后缀为真了吧。
即使建立了后缀点,那么我们确实杜绝了一个国家的所有前后缀都不是首都的情况,
但这个前后缀是首都, 并不能指向某个城市是首都。
只有前后缀不是首都,才能指向某个城市不是首都。
还是有可能一个国家全部不是首都。
所以建不建后缀的效果相同。保证的都是<=1却没保证有。(因此UOJ210也可以只前缀优化)
那么为什么这样可行呢?
假设我们构造出一组”合法”方案,但其中存在某个国家没有首都,所有前缀都为假。
观察原建边方式可知,由于”两端的城市至少要有一个首都” ,可以都是首都,
如果我们定义是首都/前缀包含首都为真,不是首都/前缀不包含首都为假:
发现城市的真只连向对应前缀的真,和对应前缀前一个前缀的假,
而对应前缀的真只会连向后一个城市的假,和后一个前缀的真,
以此类推,发现如果任意把一个城市改为真,影响的只会是之后的前缀变为了真,而这些前缀并不能指向某个城市的真。
(剩下的本来就是假的现在还是假的)
于是对于没有首都的国家,任意改变一个城市为首都是可行的。
于是对于这种建图,如果判定有解,总能找出一组合法解,究其原因就是因为“每条边两端的城市至少要有一个首都”这个条件。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N=1000100;
int n,m,k,id[N],idc=1,pl[4*N],cnt=0,stack[4*N],top=0,inc=0,dfn[4*N],low[4*N];
int head[4*N],nxt[16*N],to[16*N],num=1;
bool ins[4*N];
void build(int u,int v)
{
num++;
to[num]=v;
nxt[num]=head[u];
head[u]=num;
}
void add(int u,int v)
{
build(u,v); build(v^1,u^1);
}
void dfs(int u)
{
inc++; dfn[u]=low[u]=inc;
stack[++top]=u; ins[u]=1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(!dfn[v]) {dfs(v); low[u]=min(low[u],low[v]);}
else if(ins[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
cnt++;
while(1)
{
pl[stack[top]]=cnt;
ins[stack[top]]=0;
top--;
if(stack[top+1]==u) break;
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){idc+=2; id[i]=idc;}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(id[u]^1,id[v]);
}
for(int j=1;j<=k;j++)
{
int cn; scanf("%d",&cn);
for(int i=1;i<=cn;i++)
{
int x; scanf("%d",&x);
int pre=idc; idc+=2;
add(id[x],idc);
if(i!=1)
{
add(id[x],pre^1); //pre->id[x]
add(pre,idc);
}
}
}
for(int i=2;i<=idc;i++) if(!dfn[i]) dfs(i);
bool flag=0;
for(int i=1;i<=n;i++) if(pl[id[i]]==pl[id[i]^1]){flag=1; break;}
if(flag) printf("NIE\n");
else printf("TAK\n");
return 0;
}