【洛谷P6378】Riddle

题目

题目链接:https://www.luogu.com.cn/problem/P6378
(n) 个点 (m) 条边的无向图被分成 (k) 个部分。每个部分包含一些点。
请选择一些关键点,使得每个部分有一个关键点,且每条边至少有一个端点是关键点。
(n,m,kleq 10^6)

思路

看到一堆限制不难想到 2-sat。
把每一个点 (x) 拆成 (x)(x') 分别表示选与不选,对于图中一条边 ((x,y)),连边 ((x',y),(y',x))
接下来对于每一个部分,选择了其中一个点,那么这个部分的其他点都不可以选。但是直接连边空间是 (O(n^2)) 的,无法接受。
我们再记 (l_x)(l_x') 表示点 (x) 所在的部分中,(x) 以及之前的点恰好有一个被选,没有任何被选。显然有连边 ((x,l_x),(l_x',x'))。考虑对于这个部分相邻的两个点 (x,y) 的连边:

  • ((y,l_x'),(l_x,y'))。如果选 (y) 那么前面都不能选;如果前面选了那么不能选 (y)
  • ((l_x,l_y),(l_y',l_x'))。如果这个部分 (x) 之前选了,那么 (y) 之前必然选了;如果 (y) 之前没选,那么 (x) 之前必然没选。

然后跑 2-sat 就完事了。
但是这样只是保证了对于每一条边都选了至少一个点,且每一个部分至多选一个点,看起来并不保证每一个部分都选了点。考虑什么时候会出现一个部分没有选点的情况,当且仅当这一个块中任意两个点都没有连边(否则这两个点必须选择一个)。那么如果出现这种情况,且这个部分没有选点,只需要随便拿这一个部分的点选上即可。
至此,题目的所有限制都满足了。
时间复杂度 (O(n+m))

代码

#include <bits/stdc++.h>
using namespace std;

const int N=4000010;
int n,m,k,cnt,tot,head[N],dfn[N],low[N],col[N];
bool vis[N];
stack<int> st;

int read()
{
	int d=0; char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

struct edge
{
	int next,to;
}e[N*3];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void tarjan(int x)
{
	dfn[x]=low[x]=++tot;
	st.push(x); vis[x]=1;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if (vis[v])
			low[x]=min(low[x],dfn[v]);
	}
	if (low[x]==dfn[x])
	{
		int y; cnt++;
		do {
			y=st.top(); st.pop();
			col[y]=cnt; vis[y]=0;
		} while (y!=x);
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	n=read(); m=read(); k=read();
	for (int i=1;i<=m;i++)
	{
		int x=read(),y=read();
		add(x+n,y); add(y+n,x);
	}
	for (int i=1;i<=k;i++)
	{
		int t=read()-1,x=read(),y;
		add(x,x+2*n); add(x+3*n,x+n);
		while (t--)
		{
			y=read();
			add(y,y+2*n); add(y+3*n,y+n);
			add(y,x+3*n); add(x+2*n,y+n);
			add(x+2*n,y+2*n); add(y+3*n,x+3*n);
			x=y;
		}
	}
	tot=0;
	for (int i=1;i<=4*n;i++)
		if (!dfn[i]) tarjan(i);
	for (int i=1;i<=n;i++)
		if (col[i]==col[i+n] || col[i+2*n]==col[i+3*n])
			return printf("NIE"),0;
	printf("TAK");
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/14887210.html