「HNOI2019」校园旅行-DP

Description

给定 n n n个点, m m m条边的图。每个点有 0 / 1 0/1 0/1的标号,有 Q Q Q个询问,每次询问点对 ( u , v ) (u,v) (u,v)间是否一条路径(不一定是简单路径),满足路径经过的点的标号所形成的串是回文串。

n ≤ 5000 , m ≤ 5000000 n \leq 5000, m \leq 5000000 n5000,m5000000

Solution

f i , j f_{i,j} fi,j表示 i , j i,j i,j是否存在一条满足条件的路径。初始状态和转移显然。

考虑每次转移的复杂度太高,而这张图的点数较少,边数较多。

把连接了同一种标号的边拎出来。可以发现这些边使图形成了若干连通块。对于某个连通块,如果它是二分图,那么进行黑白染色后,同色点之间路径的长度只可能为奇数,异色点之间路径长度只可能为偶数。

不难发现,把这张二分图替换为它的一棵生成树,上述性质不变,而一个端点在这个连通块的状态的转移也不变,因为经过原图上的一条边可以替代为经过这条非树边在树上的路径(让另一个端点重复走即可)。而如果这不是二分图,意味着可以可以经过某个环改变路径的奇偶性,所以给这棵生成树加上一个自环。

对于连接不同标号的边同理。经过这样操作之后,边数不超过 2 n − 2 2n-2 2n2,直接套用上述暴力 D P DP DP即可。

#include 
using namespace std;

const int maxn = 5005, maxm = 500005;

int n, m, q;
vector<pair<int, int>> E[2][2];
int fa[maxn], col[maxn], f[maxn][maxn];
vector<int> to[maxn][2];
char s[maxn];
queue<pair<int, int>> que;

struct edge
{
    int to, next;
} e[maxm * 4];
int h[maxn], tot;

inline int gi()
{
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    int sum = 0;
    while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
    return sum;
}

inline void add(int u, int v)
{
    e[++tot] = (edge) {v, h[u]}; h[u] = tot;
    e[++tot] = (edge) {u, h[v]}; h[v] = tot;
    to[u][s[v] - '0'].push_back(v);
    to[v][s[u] - '0'].push_back(u);
}

int find(int x)
{
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}

bool merge(int x, int y)
{
    x = find(x); y = find(y);
    if (x == y) return 0;
    return fa[x] = y, 1;
}

void dfs(int u, int fa)
{
    for (int i = h[u], v; v = e[i].to, i; i = e[i].next)
        if (v != fa) col[v] = col[u] ^ 1, dfs(v, u);
}

void solve(int c1, int c2)
{
    for (int i = 1; i <= n; ++i) h[i] = 0, fa[i] = i;
    tot = 0;
    
    for (auto e : E[c1][c2]) {
        int x = e.first, y = e.second;
        if (merge(x, y)) add(x, y);
    }
    for (int i = 1; i <= n; ++i)
        if (fa[i] == i) col[i] = 0, dfs(i, 0);
    for (auto e : E[c1][c2]) {
        int x = e.first, y = e.second;
        if (col[x] == col[y]) col[find(x)] = -1;
    }
    for (int i = 1; i <= n; ++i)
        if (col[i] == -1) add(i, i);
}

void bfs()
{
    for (int i = 1; i <= n; ++i) f[i][i] = 1, que.push(make_pair(i, i));
    for (int i = 1; i <= n; ++i)
        for (int j : to[i][s[i] - '0']) if (i < j) f[i][j] = 1, que.push(make_pair(i, j));
        
    int u, v, siz1, siz2, i, j, a, b;
    while (!que.empty()) {
        u = que.front().first, v = que.front().second;
        que.pop();
        
        siz1 = to[u][0].size(); siz2 = to[v][0].size();
        for (i = 0; i < siz1; ++i)
            for (j = 0; j < siz2; ++j) {
                a = to[u][0][i], b = to[v][0][j];
                if (a > b) swap(a, b);
                if (f[a][b]) continue;				
                que.push(make_pair(a, b));
                f[a][b] = 1;
            }

        siz1 = to[u][1].size(); siz2 = to[v][1].size();
        for (i = 0; i < siz1; ++i)
            for (j = 0; j < siz2; ++j) {
                a = to[u][1][i], b = to[v][1][j];
                if (a > b) swap(a, b);
                if (f[a][b]) continue;				
                que.push(make_pair(a, b));
                f[a][b] = 1;
            }
    }
}

int main()
{
    n = gi(); m = gi(); q = gi();
    scanf("%s", s + 1);

    for (int u, v, i = 1; i <= m; ++i) {
        u = gi(); v = gi();
        if (s[u] > s[v]) swap(u, v);
        E[s[u] - '0'][s[v] - '0'].push_back(make_pair(u, v));
    }

    solve(0, 0);
    solve(1, 1);
    solve(0, 1);

    bfs();

    for (int a, b, i = 1; i <= q; ++i) {
        a = gi(); b = gi();
        if (a > b) swap(a, b);
        puts(f[a][b] ? "YES" : "NO");
    }
    
    return 0;
}

你可能感兴趣的:(算法——DP)