【HNOI2019】校园旅行

DP,BFS,优化建图

题目大意:

给定一张简单无向图,每个点的权值为 $0$ 或 $1$。

每次询问给出两点,问是否存在一条连接两点的路径(可以不是简单路径),其经过的点的权值按经过顺序排列后,形成回文串。

题解:

$n$ 比较小,我们直接令 $f[x][y]$ 表示 $x$ 到 $y$ 是否存在这样的路径。用 BFS 进行转移,每次枚举两个端点相连的点。

这样的时间复杂度为 $O(n^2+m^2+q)$。

我们需要优化建图。

观察到,往回文串两边放相同的数时,只需要能够使它们奇偶性相同即可。因为另外一端可以在一条边上一直来回。考虑如何建图能在不改变可行的奇偶性的条件下,减少边数。

考虑把连接两个同色点的边,和连接两个异色点的边分开考虑。

对于连接同色的点的边形成的每个连通块,如果这个连通块是个二分图,那么从一个点到另一个点的奇偶性都是唯一确定的。任意取它的一棵生成树不改变其奇偶性。如果不是二分图,其中必定存在奇环,那么不管奇还是偶,都可以去那个奇环上转一圈来达到改变奇偶性的目的。那么我们仍然取生成树,再在任意一个点上,连一个自环,就可以达到相同目的。

对于连接异色点的边形成的连通块,由于它本身就是个二分图,直接取生成树即可。

那么我们就把边数降为了 $O(n)$ 级别。

再跑上面的算法,时间复杂度就是 $O(n^2+m+q)$。

在 UOJ 上要通过 Hack 数据还需要一定的常数优化。比如 $f[x][y]$ 和 $f[y][x]$ 是一样的,只需要算其中一个,这里可以优化一半的常数。

Code:

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int N=5050;
vector<int>G[N];
char s[N];
int n,m,Q;
inline void AddEdgE(int u,int v){
    G[u].push_back(v);
    if(u!=v)G[v].push_back(u);
}
struct workspace{
    vector<int>G[N];
    bool vis[N];
    int dep[N];
    inline void addedge(int u,int v){G[u].push_back(v),G[v].push_back(u);}
    bool check(int now){
        for(int to:G[now])
            if(!dep[to]){
                dep[to]=dep[now]+1;
                bool ret=check(to);
                if(ret)return 1;
            }else
                if((dep[now]-dep[to]+1)&1)return 1;
        return 0;
    }
    void getedge(int now){
        vis[now]=1;
        for(int to:G[now])
            if(!vis[to]){
                AddEdgE(now,to);
                getedge(to);
            }
    }
    void work_1(){
        for(int i=1;i<=n;++i)
            if(!vis[i]){
                dep[i]=1;
                bool is_2=check(i);
                getedge(i);
                if(is_2)AddEdgE(i,i);
            }
    }
    void work_2(){
        for(int i=1;i<=n;++i)
            if(!vis[i])getedge(i);
    }
}A,B;
bool f[N][N];
int q[N*N];
void work(){
    int head=1,tail=0;
    for(int i=1;i<=n;++i){
        f[i][i]=1;
        q[++tail]=i<<16|i;
    }
    for(int i=1;i<=n;++i)
        for(int to:G[i])
            if(s[i]==s[to]&&!f[to][i]){
                f[i][to]=1;
                q[++tail]=i<<16|to;
            }
    while(head<=tail){
        int X=q[head++];
        int u=X>>16,v=X&65535;
        for(int x:G[u])
            for(int y:G[v])
                if(s[x]==s[y]&&!f[x][y]&&!f[y][x]){
                    f[x][y]=1;
                    q[++tail]=x<<16|y;
                }
    }
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m>>Q>>(s+1);
    for(int i=1;i<=m;++i){
        int u,v;
        cin>>u>>v;
        if(s[u]!=s[v])B.addedge(u,v);else A.addedge(u,v);
    }
    A.work_1(),B.work_2();
    work();
    while(Q--){
        int x,y;
        cin>>x>>y;
        cout<<(f[x][y]||f[y][x]?"YES\n":"NO\n");
    }
    return 0;
}