#2-SAT,Tarjan,前缀优化建边#洛谷 6378 [PA2010]Riddle

题目

\(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。

请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。


分析

每条边至少有一个端点是关键点很好做就是\(x'->y,y'->x\)
考虑每个部分恰有一个怎么做,不可能暴力建边,
考虑把前\(i\)个是否选新开节点,那么就得满足
\(pre_{a_{i-1}}->a_{i}'\qquad a_i->pre_{a_{i-1}}'\)(选择\(1\sim i-1\)就不能选\(i\),反之亦然)
\(a_i->pre_{a_i}\qquad pre_{a_i}'->a_i'\)(选择\(i\)就一定选择\(1\sim i\),反之亦然)
\(pre_{a_i}'->pre_{a_{i-1}}'\qquad pre_{a_{i-1}}->pre_{a_i}\)(选择\(1\sim i-1\)就一定选择\(1\sim i\),反之亦然)
这样就能强制保证每个部分有且仅有一个,套2-SAT模板就行了


代码

#include <cstdio>
#include <cctype>
#include <stack>
#define rr register
using namespace std;
const int N=4000011; struct node{int y,next;}e[N<<1];
int dfn[N],low[N],v[N],as[N],col[N],tot,cnt,et,n,m,k; stack<int>stac;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline signed min(int a,int b){return a<b?a:b;}
inline void add(int x,int y){e[++et]=(node){y,as[x]},as[x]=et;}
inline signed fal(int x){return x+n;};
inline signed tru(int x){return x;}
inline signed sub(int x){return x+n*2;}
inline void tarjan(int x){
	dfn[x]=low[x]=++tot,v[x]=1,stac.push(x);
	for (rr int i=as[x];i;i=e[i].next)
	if (!dfn[e[i].y]){
		tarjan(e[i].y);
		low[x]=min(low[x],low[e[i].y]);
	}else if (v[e[i].y])
	    low[x]=min(low[x],dfn[e[i].y]);
	if (dfn[x]==low[x]){
		rr int y; ++cnt;
		do{
			y=stac.top(),stac.pop();
			v[y]=0,col[y]=cnt;
		}while (x^y);
	}
}
signed main(){
	n=iut(); m=iut(); k=iut();
	for (rr int i=1;i<=m;++i){
		rr int x=iut(),y=iut();
		add(fal(x),tru(y)),add(fal(y),tru(x));
	}
	for (rr int i=1,t;i<=k;++i){
		t=iut();
		for (rr int j=1;j<=t;++j){
			rr int x=iut();
			add(tru(x),tru(sub(j+tot))),
			add(fal(sub(j+tot)),fal(x));
			if (j>1){
				add(tru(sub(j-1+tot)),tru(sub(j+tot))),
				add(fal(sub(j+tot)),fal(sub(j-1+tot))),
				add(tru(x),fal(sub(j-1+tot))),
				add(tru(sub(j-1+tot)),fal(x));
			}
		}
		tot+=t;
	}
	tot=0;
	for (rr int i=1;i<=n*4;++i) if (!dfn[i]) tarjan(i);
	for (rr int i=1;i<=n;++i)
	if (col[tru(i)]==col[fal(i)])
	    return !printf("NIE");
	for (rr int i=1;i<=n;++i)
	if (col[tru(sub(i))]==col[fal(sub(i))])
	    return !printf("NIE");
	return !printf("TAK");
} 
posted @ 2020-11-04 21:55  lemondinosaur  阅读(86)  评论(0编辑  收藏  举报