P6378 [PA2010] Riddle

考虑 2-sat ,这样的话我们便需要考虑建两种边:

  • 对于每一条边,我们用每一个点的零点连向另一个点的一点。
  • 对于一个部分,我们用每一个点的一点连向剩下所有点的零点。

对于第一种边非常好处理,但是对于第二种边由于 \(n\le 10^6\) 的数据范围导致我们不能暴力建边,此时便考虑一个叫做前缀优化建图的东西。

前缀优化建图可以处理两个集合两两建边,但是对应点之间不连边的情况(如图所示)。

P6378 _PA2010_ Riddle1.png

首先,由于 2-sat 的节点本身就存在一些含义,我们尽量就不要在 2-sat 的有意义的节点上动刀子,考虑给每一个节点先整一个辅助节点,将边都先放在辅助点上。然后我们考虑利用辅助点连出这么一个东西(如图所示)。

P6378 _PA2010_ Riddle2.png

考虑一个点如果要向左走,会直接走那个向左下的边,然后根据横向的调整,一个点如果要向右走,会走到相应的位置,再走右下边,而一个点并不会走到自己。这就满足了题目的要求。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,M=1e6+5;
int n,m,k,id[4][N];
struct Edge{int nxt,to;}e[N*6+M*2];int fir[N*4],e_size=0;
void add(int u,int v){e[++e_size]=(Edge){fir[u],v},fir[u]=e_size;}
int dfn[N*4],low[N*4],cnt_dfn=0,tag[N*4],vis[N*4];stack<int> s;
void tarjan(int u){
	dfn[u]=low[u]=++cnt_dfn,s.push(u),vis[u]=true;
	for(int i=fir[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		while(s.top()!=u) tag[s.top()]=u,vis[s.top()]=false,s.pop();
		tag[s.top()]=u,vis[s.top()]=false,s.pop();
	}
}
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;++i){
		for(int j=0;j<4;++j) id[j][i]=j*n+i;
	}
	for(int i=1;i<=m;++i){
		int u,v;scanf("%d%d",&u,&v);
		add(id[0][u],id[1][v]),add(id[0][v],id[1][u]);
	}
	for(int i=1;i<=k;++i){
		int K,a[N];scanf("%d",&K);
		for(int j=1;j<=K;++j) scanf("%d",&a[j]);
		for(int j=1;j<=K;++j){
			add(id[1][a[j]],id[3][a[j]]);
			add(id[2][a[j]],id[0][a[j]]);
		}
		for(int j=1;j<K;++j){
			add(id[3][a[j]],id[0][a[j+1]]);
			add(id[3][a[j]],id[3][a[j+1]]);
			add(id[1][a[j+1]],id[2][a[j]]);
			add(id[2][a[j+1]],id[2][a[j]]);
		}
	}
	for(int i=1;i<=4*n;++i) if(!dfn[i]) tarjan(i);
	for(int i=1;i<=n;++i){
		if(tag[id[0][i]]==tag[id[1][i]])
			return printf("NIE\n"),0;
	}
	return printf("TAK\n"),0;
}
posted @ 2022-01-21 11:33  Point_King  阅读(45)  评论(0编辑  收藏  举报