题解 P5292 【[HNOI2019]校园旅行】
myy出的题真的好妙啊 \(QAQ\)
\(HN-D2\)全程没人切题欸。。。
首先是暴力的做法,我们可以发现,一个回文串会满足如下性质:
将首尾去掉其仍然回文。
所以我们可以根据这个性质来做,预处理出所有点对是否可以通过回文串达。
所以我们假设已经知道了\((x,y)\)之间是回文串可达的,那么我们其实只需要枚举与\(x\),\(y\)分别相连的两个点\((u,v)\),如果这两个点相同,那么这两个点便回文串可达。
具体做法是类似于\(dp/spfa\)的一个流程。会将所有单独的点加入一个队列,如果一条边连接了两个相同的点,那么也将这两个点加入队列。
那么每次弹出队列并更新即可,注意一个点对不需要去重复更新其他点多次,也就是说每个点对只会入队/出队一次。个人感觉这样做的复杂度应该是\(O(N^2M)\),然而看了myy的题解这个复杂度是\(O(M^2)?\)
代码大致如下:
while( !q.empty() ) {
Node u = q.front(); q.pop() ;
int Fr = u.from, To = u.to ;
for( int i = head[Fr]; i; i = e[i].to ) {
int v = e[i].to ;
for( int j = head[To]; j; j = e[j].to ) {
int v2 = e[j].to ;
if( w[v] != w[v2] || dis[v][v2] ) continue ;
dis[v][v2] = dis[v2][v] = 1, q.push((Node){ v, v2 }) ;
}
}
}
然后是,看不懂的部分了 QAQ (研究了半天题解都看不懂.jpg)
注意到边数很多,然而点数较少,能不能在这上面优化?
首先只考虑连接了两个相同颜色\((0/1)\)的点的边,这样的边会将原图构成若干个联通块。
对每个联通块单独考虑,如果这个联通块是二分图,那么我们可以将点分成两类\(1,2\),不难发现从\(1\)类走到\(2\)类总是要经过奇数条边,而从某一类走回某一类还是会经过偶数条边,(假设点均为 \(1\) ),也就是途中会得到偶数/奇数个 \(1\)
然而实际上对于这个二分图只保留其生成树仍然具有这个性质。因为题目中说允许重复经过某一个点。所以我们在这个二分图中反复走环等其实经过的点数的奇偶性仍然相同。这与在其生成树上重复经过同一条边是等价的。
对于一个回文串 \(000111000\) 其可以看作先后经过了三个联通块(先假设这些联通块都是二分图)
然而实际上我们并不关注走过了多少个点0/1,只要能够使得两点间经过联通块的奇偶性相同就行了。
也就是走了多少点都无所谓了。
如果只保留原联通块(二分图)的生成树的话,不难发现对于每个联通块内部经过的奇偶数量是一样的。类似的去画下图,可以发现只保留生成树是不会对答案造成影响。
如果这个图不是二分图呢?我们发现对于不是二分图的图我们总可以通过不断地绕圈得到奇/偶的点对,所以我们需要在其生成树上加上一个自环。
对于连接了\(0\)和\(1\)的边也类似讨论,这些边组成的图显然是一个二分图(0一类,1一类)我们对其也只需要保留一颗生成树。
然后这样做的话最后总边数:\((m->n)\)
复杂度据说是\(O(N^2)???\)
#include<bits/stdc++.h>
using namespace std;
int read() {
char cc = getchar(); int cn = 0;
while(cc < '0' || cc > '9') cc = getchar();
while(cc >= '0' && cc <= '9') cn = cn * 10 + cc - '0', cc = getchar();
return cn;
}
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
const int M = 1e6 + 5 ;
const int N = 5000 + 5;
struct E {
int to, next ;
} e[M * 2];
struct S {
int from, to ;
} s[M * 2];
int n, m, Q, head[N], cnt, w[N], dis[N][N], fa[N][2], col[N], sd[N], book[N];
char ss[N] ;
queue< S> q ;
void add( int x, int y ) {
e[++ cnt] = (E){ y, head[x] }, head[x] = cnt ;
e[++ cnt] = (E){ x, head[y] }, head[y] = cnt ;
}
int abc( int x ) { //绝对值
return ( x > 0 ) ? x : -x ;
}
int find( int x, int node ) {
if( fa[x][node] == x ) return x ;
return fa[x][node] = find(fa[x][node], node) ;
}
void dfs( int x, int c ) {
col[x] = c ;
Next( i, x ) {
int v = e[i].to ;
if( !col[v] ) dfs( v, -c ) ;
else if( col[v] == col[x] ) sd[abc(c)] = 1 ;
}
}
void init() {
rep( i, 1, n ) if( !col[i] ) dfs( i, i ) ; //二分图染色
memset( head, 0, sizeof(head) ), cnt = 0 ;
rep( i, 1, m ) {
int Fr = s[i].from, To = s[i].to ;
if( w[Fr] != w[To] ) {
int u = find( Fr, 1 ), v = find( To, 1 ) ;
if( u == v ) continue ;
add( Fr, To ), fa[u][1] = v ;
}
else {
int u = find(Fr, 0), v = find(To, 0) ;
if( u == v ) continue ;
add( Fr, To ), fa[u][0] = v ;
q.push((S){ Fr, To }), dis[Fr][To] = dis[To][Fr] = 1 ;
}
}
rep( i, 1, n ) {
int cc = abc(col[i]) ;
if( sd[cc] && ( book[cc] == 0 ) ) add( cc, cc ), book[cc] = 1 ; //自环
}
}
void spfa() { //spfa求解两个点是否可达
while( !q.empty() ) {
S u = q.front(); q.pop() ;
int Fr = u.from, To = u.to ;
Next( i, Fr ) {
int v = e[i].to ;
Next( j, To ) {
int v2 = e[j].to ;
if( w[v] != w[v2] || dis[v][v2] ) continue ;
dis[v][v2] = dis[v2][v] = 1, q.push((S){v, v2}) ;
}
}
}
}
void output() { //输出文件
int x, y ;
rep( i, 1, Q ) {
x = read(), y = read() ;
if( dis[x][y] ) puts("YES") ;
else puts("NO") ;
}
}
signed main()
{
n = read(), m = read(), Q = read() ;
scanf("%s", ss);
rep( i, 1, n ) w[i] = ss[i - 1] - '0', dis[i][i] = 1, fa[i][0] = fa[i][1] = i, q.push((S){i, i}) ;
rep( i, 1, m ) {
s[i].from = read(), s[i].to = read();
if( w[s[i].from] == w[s[i].to] ) add( s[i].from, s[i].to ) ;
}
init(), spfa(), output() ;
return 0;
}