题目
题目链接: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;
}