【洛谷P6378】Riddle
题目
题目链接:https://www.luogu.com.cn/problem/P6378
\(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。
请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。
\(n,m,k\leq 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;
}