@loj - 3057@「HNOI2019」校园旅行
@description@
某学校的每个建筑都有一个独特的编号。一天你在校园里无聊,决定在校园内随意地漫步。
你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你情不自禁的把周围每个建筑的编号都记了下来——但其实你没有真的记下来,而是把每个建筑的编号除以 2 取余数得到 0 或 1,作为该建筑的标记,多个建筑物的标记连在一起形成一个 01 串。
你对这个串很感兴趣,尤其是对于这个串是回文串的情况,于是你决定研究这个问题。
学校可以看成一张图,建筑是图中的顶点,而某些顶点之间存在无向边。对于每个顶点我们有一个标记(0 或者 1)。每次你会选择图中两个顶点,你想知道这两个顶点之间是否存在一条路径使得路上经过的点的标记形成一个回文串。
一个回文串是一个字符串使得它逆序之后形成的字符串和它自己相同,比如 010,1001 都是回文串,而 01,110 不是。注意长度为 1 的串总是回文串,因此如果询问的两个顶点相同,这样的路径总是存在。此外注意,经过的路径不一定为简单路径,也就是说每条边每个顶点都可以经过任意多次。
输入格式
第一行三个整数𝑛,𝑚,𝑞,表示图中的顶点数和边数,以及询问数。
第二行为一个长度为𝑛的01串,其中第𝑖个字符表示第i个顶点(即顶点i)的标记,点从1开始编号。
接下来𝑚行,每一行是两个整数𝑢𝑖,𝑣𝑖,表示顶点𝑢𝑖和顶点𝑣𝑖之间有一条无向边,不存在自环或者重边。
接下来𝑞行,每一行存在两个整数𝑥𝑖,𝑦𝑖,表示询问顶点𝑥𝑖和顶点𝑦𝑖的点之间是否有一条满足条件的路径。
输出格式
输出𝑞行,每行一个字符串“YES”,或者“NO”(引号不输出)。输出“YES”表示满足条件的路径存在,输出“NO”表示不存在。
样例输入 1
5 4 2
00010
4 5
1 3
4 2
2 5
3 5
1 3
样例输出 1
NO
YES
样例说明 1
对于第一个询问,3 号点和 2 号点不连通, 因此答案为 NO。
对于第二个询问,一条合法的路径是 1 -> 3 ,路径上的标号形成的字符串为 00。注意合法路径不唯一。
数据范围与提示
1 <= n <= 5*10^3, 1 <= m <= 5*10^5, 1 <= q <= 10^5。
@solution@
不愧是 myy 出的题.jpg。
考虑一个朴素的算法:我们记 f(x, y) 表示 x 到 y 是否能够拼出回文串。
每次枚举 x 相邻一个点 u 与 y 相邻的一个点 v,如果 u, v 标记相同则从 f(u, v) 转移到 f(x, y)。
但是这个转移式带环啊。我们可以考虑从合法的状态 f(u, v) 向外 bfs,曾经访问过的就不访问。即将从回文串两端收缩改成从回文串中心扩张出去。
这个算法中,每对边最多考虑一次,故复杂度为 O(m^2)。
考虑直接对这个朴素算法优化。注意复杂度跟边有关,我们可否删去一些边,使得合法状态依然合法?
注意到 n 其实只有 5000,也就是说 O(n^2) 的算法完全可以跑过。初步表明这个思路或许是正确的。
考虑一个标记相同的连通块,进去和出来将得到连续的 0/1。
当这个连通块大小为 1 时,只能贡献 1 个 0/1。
当这个连通块为二分图时,如果进去的点 x 与出来的点 y 在同一侧,可以贡献任意多的奇数连续 0/1;否则贡献任意多的偶数连续 0/1。
当这个连通块包含奇环时,总是可以贡献任意多的连续 0/1。
当连通块为二分图/大小为 1 时,仅保留一个生成树性质不变。
当连通块包含奇环时,可以通过保留生成树 + 一个自环(自环也是奇环)而性质不变。
为了更好的理解,我们不妨把点权转为边权。
当一条边连接的两个端点权值不同,记边权为 0;否则记边权为 1。
这样,只有起点与终点的权值相同,且存在一条边权回文的路径时才合法。
对于权值相同的边形成的连通块,可以通过上面的方法保留生成树/生成树 + 自环保持性质不变。
最终边就被缩到了 O(n) 条,就可以通过本题了。
@accepted code@
#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;
const int MAXN = 5000;
const int MAXM = 500000;
int fa[MAXN + 5], n, m, q;
struct Graph {
struct edge {
edge *nxt;
int to;
} edges[2 * MAXM + 5], *adj[MAXN + 5], *ecnt;
Graph() { ecnt = edges; }
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
// printf("! %d %d\n", u, v);
}
} G1, G2, G3;
bool flag;
int clr[MAXN + 5];
void dfs(int x, int c) {
clr[x] = c;
for (Graph::edge *p = G1.adj[x]; p; p = p->nxt) {
if (clr[p->to] != -1) {
if (clr[p->to] == clr[x])
flag = false;
} else
G2.addedge(x, p->to), dfs(p->to, c ^ 1);
}
}
void dfs2(int x) {
clr[x] = 1;
for (Graph::edge *p = G3.adj[x]; p; p = p->nxt) {
if (clr[p->to] == -1)
G2.addedge(x, p->to), dfs2(p->to);
}
}
void get() {
for (int i = 1; i <= n; i++) clr[i] = -1;
for (int i = 1; i <= n; i++)
if (clr[i] == -1) {
flag = true, dfs(i, 0);
if (!flag)
G2.addedge(i, i);
}
for (int i = 1; i <= n; i++) clr[i] = -1;
for (int i = 1; i <= n; i++)
if (clr[i] == -1)
dfs2(i);
}
bool f[MAXN + 5][MAXN + 5], g[MAXN + 5][MAXN + 5];
bool tag[MAXN + 5];
char s[MAXN + 5];
int u[MAXM + 5], v[MAXM + 5];
int main() {
scanf("%d%d%d", &n, &m, &q);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++) tag[i] = s[i] - '0', fa[i] = i;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &u[i], &v[i]);
if (tag[u[i]] == tag[v[i]])
G1.addedge(u[i], v[i]);
else
G3.addedge(u[i], v[i]);
}
get();
queue<pair<int, int> > que;
for (int i = 1; i <= n; i++) que.push(make_pair(i, i)), g[i][i] = true;
for (int i = 1; i <= n; i++)
for (Graph::edge *p = G2.adj[i]; p; p = p->nxt)
if (tag[p->to] == tag[i])
que.push(make_pair(i, p->to)), g[i][p->to] = g[p->to][i] = true;
while (!que.empty()) {
pair<int, int> x = que.front();
que.pop();
int u = x.first, v = x.second;
f[u][v] = f[v][u] = true;
for (Graph::edge *p = G2.adj[u]; p; p = p->nxt)
for (Graph::edge *q = G2.adj[v]; q; q = q->nxt)
if (!g[p->to][q->to] && tag[p->to] == tag[q->to])
que.push(make_pair(p->to, q->to)), g[p->to][q->to] = g[q->to][p->to] = true;
}
for (int i = 1; i <= q; i++) {
int x, y;
scanf("%d%d", &x, &y);
puts(f[x][y] ? "YES" : "NO");
}
}
@details@
没有考虑到连通块中还存在点大小为 1 的情况,然后就想歪了。
然后就歪不回来了。