luogu P6378 [PA 2010] Riddle / QOJ10674 Riddle [A]
很容易想到 2-SAT。困难的地方在于怎么表示每个区域恰好一个点,如果仅仅把一个点 \(i\) 分为 \(i_0,i_1\) 两个点表示是否关键的话,连边的数量是 \(n^2\) 的。对于每一个区域,我们考虑前缀优化建图,再新建两个点 \(i_2,i_3\) 表示前 \(i\) 个已经选了一个和前 \(i\) 个一个都没选的情况。
建图方式如下:

对于图中的边 \((i,j)\),我们直接让 \(i_0\) 连向 \(j_1\),\(i_1\) 连向 \(j_0\) 即可,这是平凡的。
需要注意的是这样连边虽然能保证每个区域不多于一个关键点,但并不能保证每个区域都至少有一个关键点,对于没有关键点的区域,我们随便选一个当关键点就行了。
#include<bits/stdc++.h>
using namespace std;
const int N=4e6+5,M=1e6+5;
int n,m,k,dfn[N],low[N],idx,cnt,bl[N],pos[N],ans[M],to[N<<1],nxt[N<<1],head[N],tot,st[N],top;
inline void add(int u,int v){
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
inline void dfs(int x){
dfn[x]=low[x]=++idx;
st[++top]=x;
for(int i=head[x],v;i;i=nxt[i]){
v=to[i];
if(!dfn[v]){
dfs(v);
low[x]=min(low[x],low[v]);
}else if(!bl[v])low[x]=min(low[x],dfn[v]);
}
if(dfn[x]==low[x]){
cnt++;int u;
do{
u=st[top];top--;
bl[u]=cnt;
}while(u!=x);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
add(u,v+n);
add(v,u+n);
}
for(int i=1,l,lst,x;i<=k;i++){
cin>>l;lst=0;
for(int j=1;j<=l;j++){
cin>>x;pos[x]=i;
add(x+n,x+2*n);
add(x+3*n,x);
if(j>1){
add(lst+2*n,x+2*n);
add(x+3*n,lst+3*n);
add(lst+2*n,x);
add(x+n,lst+3*n);
}
lst=x;
}
}
for(int i=1;i<=4*n;i++)
if(!dfn[i])
dfs(i);
for(int i=1;i<=n;i++)
if(bl[i]==bl[i+n]||bl[i+2*n]==bl[i+3*n]){//这里也可以只判断其中一对,但我不知道这是为什么,或许需要感性理解一下
cout<<"NIE";
return 0;
}
cout<<"TAK\n";
//QOJ 需输出方案
for(int i=1;i<=n;i++)if(bl[i+n]<bl[i])ans[pos[i]]=i;
for(int i=1;i<=n;i++)if(!ans[pos[i]])ans[pos[i]]=i;
for(int i=1;i<=k;i++)cout<<ans[i]<<" ";
return 0;
}

浙公网安备 33010602011771号