Problem

Description

某学校的每个建筑都有一个独特的编号。一天你在校园里无聊,决定在校园内随意地漫步。

你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你情不自禁的把周围每个建筑的编号都记了下来——但其实你没有真的记下来,而是把每个建筑的编号除以 \(2\) 取余数得到 \(0\) 或 \(1\),作为该建筑的标记,多个建筑物的标记连在一起形成一个 \(01\) 串。

你对这个串很感兴趣,尤其是对于这个串是回文串的情况,于是你决定研究这个问题。

学校可以看成一张图,建筑是图中的顶点,而某些顶点之间存在无向边。对于每个顶点我们有一个标记(\(0\) 或者 \(1\))。每次你会选择图中两个顶点,你想知道这两个顶点之间是否存在一条路径使得路上经过的点的标记形成一个回文串

一个回文串是一个字符串使得它逆序之后形成的字符串和它自己相同,比如 \(010\),\(1001\) 都是回文串,而 \(01\),\(110\) 不是。注意长度为 \(1\) 的串总是回文串,因此如果询问的两个顶点相同,这样的路径总是存在。此外注意,经过的路径不一定为简单路径,也就是说每条边每个顶点都可以经过任意多次。

Input Format

第一行三个整数 \(n,m,q\),表示图中的顶点数和边数,以及询问数。

第二行为一个长度为 \(n\) 的 \(01\) 串,其中第 \(n\) 个字符表示第 \(i\) 个顶点(即顶点 \(i\))的标记,点从 \(1\) 开始编号。

接下来 \(m\) 行,每一行是两个整数 \(u_i,v_i\),表示顶点 \(u_i\) 和顶点 \(v_i\) 之间有一条无向边,不存在自环或者重边。

接下来 \(q\) 行,每一行存在两个整数 \(x_i,y_i\),表示询问顶点 \(x_i\) 和顶点 \(y_i\) 的点之间是否有一条满足条件的路径。

Output Format

输出 \(q\) 行,每行一个字符串 YES,或者 NO。输出 YES 表示满足条件的路径存在,输出 NO 表示不存在。

Sample

Input 1

5 4 2
00010
4 5
1 3
4 2
2 5
3 5
1 3

Output 1

NO
YES

Input 2

10 11 10
0011011111
4 6
10 6
5 9
4 7
10 7
5 8
1 9
5 7
1 10
5 1
5 6
10 3
7 4
8 10
9 4
8 9
6 6
2 2
9 9
10 9
3 4

Output 2

NO
YES
YES
NO
YES
YES
YES
YES
YES
NO

Explanation

Explanation for Input 1

对于第一个询问,\(3\) 号点和 \(2\) 号点不连通, 因此答案为 NO

对于第二个询问,一条合法的路径是 \(1 \to 3\),路径上的标号形成的字符串为 \(00\)。注意合法路径不唯一。

Range

对于 \(30\%\) 的数据,\(1 \le m \le 10^4\);

对于 \(70\%\) 的数据,\(1\le n\le 3\times 10^3,1 \le m \le 5\times 10^4\);

对于 \(100\%\) 的数据,\(1 \le n \le 5\times 10^3, 1 \le m \le 5\times 10^5, 1 \le q \le 10^5\)。

Algorithm

\(DP\)

Mentality

考场上无人切的神题 \(orz\) ,\(myy\ nb\) 。

由于 \(n\) 异常的小,所以我们发现完全可以用 \(n^2\) 的二维空间来储存信息,而 \(m\) 和 \(q\) 相对来说又异常大,这启发我们用一种看起来很暴力的方法做这道题 -- 预处理出所有点对的情况。

\(30\) 分的做法还是很好想的,我们发现可以将回文路径分为两类:长度为奇数的,长度为偶数的。

设 \(f[i][j]\) 为 \(i,j\) 之间是否有回文路径,那么我们先处理出长度最短的奇偶回文路径。首先,长度最短的奇数回文路径就是每个点自己,即 \(f[i][i]=1\) 。然后观察到对于每条边,如果连的两个点 \(u,v\) 编号相同,则 \(f[u][v]=f[v][u]=1\) ,这些就是长度最短的偶数回文路径。

然后考虑利用这些信息进行 \(DP\) 转移,我们用 \(bfs\) 的顺序来转移即可。

将这些两点之间有路径的二元组 \((u,v)\) 扔进队列,转移的时候枚举 \(u,v\) 的出边 \(to_u,to_v\),如果 \(to_u\) 的编号与 \(to_v\) 相同,那么 \(to_u,to_v\) 之间肯定也存在回文路径,我们将 \(f\) 数组更新,然后将二元组 \(to_u,to_v\) 丢入队列末尾等待下一次转移即可。

由于每次转移都要枚举两点的出边一一判断,所以复杂度为 \(m^2\) 。

代码大概长这个样子?

while(h<t)
{
h++;
for(int i=hd[u[h]];i;i=Nx[i])
for(int j=hd[v[h]];j;j=Nx[j])
if(S[To[i]]==S[To[j]]&&!f[To[i]][To[j]])
f[To[i]][To[j]]=f[To[j]][To[i]]=1,Add(To[i],To[j]);
}

由于 \(STL\) 常数太大,所以手写队列 (也就总共 \(5e7\) 的空间而已)

询问一次就直接看它的 \(f\) 数组即可。

接下来考虑 \(100\) 分做法。

观察到 \(m\) 巨大,我们考虑减少边的枚举。

我们将所有转移分成两类:向相同编号的点转移,向不同编号的点转移。

那么我们也就可以依此将边分为两类:连接相同编号点的边,连接不同编号点的边。

我们先考虑一类边,譬如连接相同编号点的边。

这些边把图分成了许多个联通块,我们发现,对于一个联通块内的转移,只取决于一件事:这个联通块是不是个二分图。

为什么呢?我们来考虑一下,如果联通块是一个二分图,那么它满足两个性质。

  • 能将联通块内的点划分成两个集合,同一集合内的点互不直接相连。

  • 同时由于这是个联通块,两点之间皆可达。

那么不难发现,如果我在一个集合内,想要转移到集合内另一点,必定会经过偶数条边。

因为我到达这个点的过程中,注定只能是重复 当前集合 -> 另一集合 -> 当前集合 这样的步骤,所以最后的步数一定是偶数条。

换而言之,若联通块为二分图,那么联通块内任意两点之间的路径长度奇偶性唯一。

而注意到,当我们 \(DP\) 转移的时候,若回文串两端新增的数字全都相同,譬如在左右端都添上 \(0\) ,那么我们只需要保证左右两边新增的数量相同即可。

而根据题目的性质可知,我们为保证数量相等,完全可以在一条连向一个相同编号点的边上来回横走保证数量的增值。但是这样不改变奇偶性

那么奇偶性就成了判断 \(DP\) 转移的重要性质了。

接着上面的推论,由于若联通块为二分图,那么联通块内两点件路径长度奇偶性唯一。那么我们在这个联通块内,只需要保留一颗生成树即可,因为 奇偶唯一 ,所以 不影响 \(DP\) 过程

然后我们再来看,如果不是二分图怎么办。还是划分成两个集合,那么同一集合内至少有一对点 \((u,v)\) 之间直接有连边。由于只考虑连接两个不同集合的边时,从 \(u\) 至 \(v\) 必定有一条长度为 偶数 的路径,所以再加上一条边,这个联通块内就有了一个 奇环 。则联通块内任意一点都可以走到奇环上通过绕环改变路径长度奇偶性。

那么不难发现,我们只需要先像二分图一样,保留一颗生成树。至于那个奇环,我们只需要在生成树内任意一个点上随便连个自环就行了 \(QwQ\),反正只是要个奇环而已,自环当然也是啦。

以上是连接相同编号点的边的处理方式。

至于连接不同编号的边的话,我们发现这联通块肯定就是个二分图,那么直接保留生成树即可。

那么边数减少为 \(n\) 的级别,此时再去 \(DP\) ,复杂度就降为 \(n^2\) 了。

如若未懂详见代码

Code

#include <cstdio>
#include <iostream>
using namespace std;
int n, m, Q;
int cntr, head[5001], nx[1000001], to[1000001], col[5001];
int cr, hd[5001], Nx[1000001], To[1000001];
int h, t, u[25000001], v[25000001];
bool flag, f[5001][5001];
char S[5001];
struct node {
int u, v;
};
void read(int &x) {
x = 0;
char ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
}
void addr(int u, int v) {
cntr++;
nx[cntr] = head[u], to[cntr] = v;
head[u] = cntr;
}
void Addr(int u, int v) {
cr++;
Nx[cr] = hd[u], To[cr] = v;
hd[u] = cr;
}
void Add(int U, int V) {
t++;
u[t] = U, v[t] = V;
}
void dye(int x, bool type) {
for (int i = head[x]; i; i = nx[i]) {
int p = to[i];
if ((S[p] == S[x]) == type) //构图参数的使用
{
if (col[p] != -1)
if (!(col[p] ^ col[x])) flag = 1; //染色冲突则不是二分图
if (col[p] == -1) {
col[p] = col[x] ^ 1, dye(p, type);
Addr(x, p), Addr(p, x); //加边
}
}
}
}
void Read_Init() {
read(n), read(m), read(Q);
scanf("%s", S + 1);
int u, v;
while (m--) {
read(u), read(v);
if (S[u] == S[v])
f[u][v] = f[v][u] = 1, Add(u, v); //先加入偶数最短边,并更新 DP 数组
addr(u, v), addr(v, u);
}
}
void Build_Init() {
for (int i = 1; i <= n; i++)
f[i][i] = 1, Add(i, i); //加入奇数最短边,并更新 DP 数组
for (int k = 0; k < 2; k++) // k 是构图参数
{
for (int i = 1; i <= n; i++) col[i] = -1; //二分图染色初始化
for (int i = 1; i <= n; i++)
if (col[i] == -1) {
flag = 0;
col[i] = 0, dye(i, k);
if (flag) Addr(i, i); //如果不是二分图那就加个自环
}
}
}
void DP() {
while (h < t) {
h++;
for (int i = hd[u[h]]; i; i = Nx[i])
for (int j = hd[v[h]]; j; j = Nx[j])
if (S[To[i]] == S[To[j]] && !f[To[i]][To[j]])
f[To[i]][To[j]] = f[To[j]][To[i]] = 1, Add(To[i], To[j]); // DP 转移
}
}
void Answer() {
int u, v;
while (Q--) {
read(u), read(v);
if (f[u][v])
printf("YES\n");
else
printf("NO\n");
}
}
int main() {
Read_Init();
Build_Init();
DP();
Answer();
}

【HNOI 2019】校园旅行的更多相关文章

  1. 【BZOJ5492】[HNOI2019]校园旅行(bfs)

    [HNOI2019]校园旅行(bfs) 题面 洛谷 题解 首先考虑暴力做法怎么做. 把所有可行的二元组全部丢进队列里,每次两个点分别向两侧拓展一个同色点,然后更新可行的情况. 这样子的复杂度是\(O( ...

  2. Loj #3057. 「HNOI2019」校园旅行

    Loj #3057. 「HNOI2019」校园旅行 某学校的每个建筑都有一个独特的编号.一天你在校园里无聊,决定在校园内随意地漫步. 你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你 ...

  3. [HNOI2019]校园旅行(构造+生成树+动规)

    题目 [HNOI2019]校园旅行 做法 最朴素的做法就是点对扩展\(O(m^2)\) 发现\(n\)比较小,我们是否能从\(n\)下手减少边数呢?是肯定的 单独看一个颜色的联通块,如果是二分图,我们 ...

  4. LOJ#3054. 「HNOI 2019」鱼

    LOJ#3054. 「HNOI 2019」鱼 https://loj.ac/problem/3054 题意 平面上有n个点,问能组成几个六个点的鱼.(n<=1000) 分析 鱼题,劲啊. 容易想 ...

  5. HNOI 2019 多边形

    HNOI 2019 多边形 题意 小 R 与小 W 在玩游戏. 他们有一个边数为\(n\)的凸多边形,其顶点沿逆时针方向标号依次为\(1,2,3...n\).最开始凸多边形中有\(n\)条线段,即多边 ...

  6. [HNOI2019]校园旅行(建图优化+bfs)

    30分的O(m^2)做法应该比较容易想到:令f[i][j]表示i->j是否有解,然后把每个路径点数不超过2的有解状态(u,v)加入队列,然后弹出队列时,两点分别向两边搜索边,发现颜色一样时,再修 ...

  7. Luogu P5292 [HNOI2019]校园旅行

    非常妙的一道思博题啊,不愧是myy出的题 首先我们考虑一个暴力DP,直接开一个数组\(f_{i,j}\)表示\(i\to j\)的路径能否构成回文串 考虑直接拿一个队列来转移,队列里存的都是\(f_{ ...

  8. 「HNOI 2019」白兔之舞

    一道清真的数论题 LOJ #3058 Luogu P5293 题解 考虑$ n=1$的时候怎么做 设$ s$为转移的方案数 设答案多项式为$\sum\limits_{i=0}^L (sx)^i\bin ...

  9. UOJ#465. 【HNOI2019】校园旅行 其他

    原文链接www.cnblogs.com/zhouzhendong/p/UOJ465.html 前言 tmd并查集写挂,调到自闭. cly和我写挂了同一个地方. 一下救了两个人感觉挺开心. 题解 首先直 ...

随机推荐

  1. 【托业】【全真题库】TEST3-语法题

    101. sales representative 销售代表 keep one's promise with 遵守对……的诺言,信守对……的承诺 107. express interest in 表现 ...

  2. 20190429 照片里面的GPS信息确实会暴露经纬度

    这是我用Android手机拍摄的照片,并上传了原图(当然在没开启定位的工作的话,照片也没有GPS这个属性显示) 2. 之前也有一种关于给陌生人点赞,通过点赞来查看你与这个陌生人的距离,我也测试了一下有 ...

  3. DDD领域驱动

    DDD领域驱动领域驱动模型.模型驱动代码接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离.DDD让你首先考虑的是业务语言而不 ...

  4. svn 安装

    SVN简介: 为什么要使用SVN? 程序员在编写程序的过程中,每个程序员都会生成很多不同的版本,这就需要程序员有效的管理代码,在需要的时候可以迅速,准确取出相应的版本. Subversion是什么? ...

  5. UGUI背包系统

    在Unity3d中,UGUI提供了Scroll Rect.Grid Layout Group.Mask这三个组件,下面就给大家介绍下如何用这个三个组件来实现滚动视图. 首先放置好背包的背景图 在矩形线 ...

  6. PHP yii框架FormWidget组件

    本篇文章介绍的是PHP yii框架Form组件,方便在view层更好调用此功能,话不多说上代码:1.先继承yii本身Widget类 <?php/** * User: lsh */ namespa ...

  7. MyBatis探究-----配置数据源的几种方式

    1.在核心配置文件mybatis-config.xml中配置数据库连接信息 mysql的j驱动jar包是mysql-connector-java-6.0.6.jar mysql版本5.7 <?x ...

  8. 关于config文件中AppSettings和ConnectionStrings的用法跟区别(转)

    转自:http://www.cnblogs.com/bindot/archive/2013/03/07/def.html

  9. Java RSA 公钥加密私钥解密

    package com.lee.utils; import java.io.DataInputStream; import java.io.File; import java.io.FileInput ...

  10. WIN10下微信崩溃(已经是最新版)的解决方法

    微信运行错误---------------------------你的微信崩溃次数较多,建议使用最新版本,点击"确定"到官网(http://pc.weixin.qq.com/)下载 ...