[PA2010] Riddle
题目大意
\(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。
请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。
题目分析
明显是 \(\verb!2-SAT!\) 题。基础的 \(\verb!2-SAT!\) 只是可以去模板题学一下,只要会 \(\verb!tarjan!\) 应该就没问题。
首先会给定 \(m\) 条边,对于一条边 \((u,v)\),因为每一条边至少一个端点是关键点,将每个点拆分成两个表示状态的点 \(u,u'\),我们就可以连边 \(u'\to v\) 和 \(v'\to u\)。\(u'\) 表示不选点 \(u\),\(u\) 表示选点 \(u\)。
(建图算法一)
对于另一个条件:一个部分只能选一个点。我们这样想:既然只能选一个点,那么就不能选其它点,所以 \(x\) 连向其它点 \(v'\) 就好了,因为 \(v'\) 就代表不选其他点 \(v\) 的状态。
这样子单次复杂度是 \(\mathcal{O(n)}\) 的,所以我们需要优化建图。
引入正题——前后缀优化建图。
按照算法一连出的图是这样的:
我们发现连的边太多了,每新加一个点就会再连 \(3\) 条边,我们要找到一种连边总数更少的建图方式。
引入“前后缀优化建图”——
我们连边可以优化一下,如图:
显然 \(9\to 15\) 等价于 \(9\to 10\to 15\),而类似的,我们会发现只要将第二层和第三层每一层的每个节点之间都互相连边就可以了,然后再稍稍优化下得到:
这样,如果我们要新加一个点,那么直接连这个点在“前缀层”中的前一个点,以及这个点在“后缀层”中的后一个点,然后惊喜的发现满足条件了。
然后代码就简单了,连边部分代码:
for (register int i = 1;i <= m; ++ i) {
int u = read(),v = read();
//u,v 至少有一个关键点
add(u + n,v),add(v + n,u);
}
//2 * n + 1 ~ 3 * n 存前缀
//3 * n + 1 ~ 4 * n 存后缀
while (k --) {
int t = read();
for (register int i = 1;i <= t; ++ i) {
a[i] = read();
add(a[i],a[i] + 2 * n),add(a[i] + 3 * n,a[i] + n);
}
for (register int i = 2;i <= t; ++ i) {
add(a[i - 1] + 2 * n,a[i] + 2 * n),add(a[i - 1] + 2 * n,a[i] + n);
add(a[i] + 3 * n,a[i - 1] + 3 * n),add(a[i],a[i - 1] + 3 * n);
}
}
代码
主要代码已经给出,其它都是板子。
// Problem: P6378 [PA2010] Riddle
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6378
// Memory Limit: 500 MB
// Time Limit: 3000 ms
// Date:2022-05-25 21:13
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <numeric>
#include <algorithm>
#include <stack>
#define enter putchar(10)
#define debug(c,que) std::cerr << #c << " = " << c << que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i = (st);i <= (ed); ++ i) std::cout << arr[i] << w;
#define speed_up() std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0)
#define mst(a,k) memset(a,k,sizeof(a))
#define stop return(0)
const int mod = 1e9 + 7;
inline int MOD(int x) {
if(x < 0) x += mod;
return x % mod;
}
namespace Newstd {
char buf[1 << 21],*p1 = buf,*p2 = buf;
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 21,stdin),p1 == p2) ? EOF : *p1 ++;
}
inline int read() {
int ret = 0,f = 0;char ch = getc();
while (!isdigit(ch)) {
if(ch == '-') f = 1;
ch = getc();
}
while (isdigit(ch)) {
ret = (ret << 3) + (ret << 1) + ch - 48;
ch = getc();
}
return f ? -ret : ret;
}
inline void write(int x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace Newstd;
const int N = 8e6 + 5;
struct Graph {
int v,nxt;
} gra[N];
int a[N],head[N],dfn[N],low[N],col[N];
bool in_stack[N];
std::stack<int>st;
int n,m,k,idx,num,cnt;
inline void add(int u,int v) {
gra[++ idx] = (Graph){v,head[u]},head[u] = idx;
}
inline void tarjan(int now) {
dfn[now] = low[now] = ++ num,in_stack[now] = true;
st.push(now);
for (register int i = head[now];i;i = gra[i].nxt) {
int v = gra[i].v;
if (!dfn[v]) {
tarjan(v);
low[now] = std::min(low[now],low[v]);
} else if (in_stack[v]) {
low[now] = std::min(low[now],dfn[v]);
}
}
if (dfn[now] == low[now]) {
cnt ++;
int u;
do {
u = st.top();st.pop();
col[u] = cnt,in_stack[u] = false;
} while (u != now);
}
}
int main(void) {
n = read(),m = read(),k = read();
for (register int i = 1;i <= m; ++ i) {
int u = read(),v = read();
//u,v 至少有一个关键点
add(u + n,v),add(v + n,u);
}
//2 * n + 1 ~ 3 * n 存前缀
//3 * n + 1 ~ 4 * n 存后缀
while (k --) {
int t = read();
for (register int i = 1;i <= t; ++ i) {
a[i] = read();
add(a[i],a[i] + 2 * n),add(a[i] + 3 * n,a[i] + n);
}
for (register int i = 2;i <= t; ++ i) {
add(a[i - 1] + 2 * n,a[i] + 2 * n),add(a[i - 1] + 2 * n,a[i] + n);
add(a[i] + 3 * n,a[i - 1] + 3 * n),add(a[i],a[i - 1] + 3 * n);
}
}
for (register int i = 1;i <= 4 * n; ++ i) {
if (!dfn[i]) {
tarjan(i);
}
}
for (register int i = 1;i <= n; ++ i) {
if (col[i] == col[i + n] || col[i + 2 * n] == col[i + 3 * n]) {
puts("NIE");
return 0;
}
}
puts("TAK");
return 0;
}