洛谷题单指南-图论之树-P5836 [USACO19DEC] Milk Visits S

原题链接:https://www.luogu.com.cn/problem/P5836

题意解读:树中节点有两种状态:G、H,给m个路径a->b,如果路径上有一个状态是c值,则输出1,否则输出0。

解题思路:

1、勤奋的做法:树链剖分

理解了题意,第一想到的就是树链剖分,通过线段树节点维护区间是否包括G、是否包括H,这两个状态都很容易通过区间合并得到,剩下就是常规的树链剖分以及线段树区间查询,直接给出完整代码。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;

vector<int> g[N];
int siz[N], depth[N], fa[N], son[N], dfn[N], rk[N], top[N], cnt; //树链剖分相关
char a[N]; //节点值
struct Node
{
    int l, r;
    bool hasG, hasH;
} tr[N * 4]; //线段树
int n, m;

void dfs1(int u, int p, int d)
{
    depth[u] = d;
    fa[u] = p;
    siz[u] = 1;
    for(auto v : g[u])
    {
        if(v == p) continue;
        dfs1(v, u, d + 1);
        siz[u] += siz[v];
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int t)
{
    top[u] = t;
    dfn[u] = ++cnt;
    rk[cnt] = u;
    if(son[u]) dfs2(son[u], t);
    for(auto v : g[u])
    {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

void pushup(Node &root, Node &left, Node &right)
{
    root.hasG = left.hasG | right.hasG;
    root.hasH = left.hasH | right.hasH;
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        if(a[rk[l]] == 'G') tr[u].hasG = true;
        else tr[u].hasH = true;
    }
    else
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) return tr[u];
    else if(tr[u].l > r || tr[u].r < l) return {0, 0, 0, 0};
    else 
    {
        Node res = {0, 0, 0, 0};
        Node left = query(u << 1, l, r);
        Node right = query(u << 1 | 1, l, r);
        pushup(res, left, right);
        return res;
    }
}

int queryPath(int u, int v, char target)
{
    Node ans = {0, 0, 0, 0};
    while(top[u] != top[v])
    {
        if(depth[top[u]] < depth[top[v]]) swap(u, v);
        Node res = query(1, dfn[top[u]], dfn[u]);
        pushup(ans, ans, res);
        u = fa[top[u]];
    }
    if(depth[u] > depth[v]) swap(u, v);
    Node res = query(1, dfn[u], dfn[v]);
    pushup(ans, ans, res);
    if(target == 'G' && ans.hasG || target == 'H' && ans.hasH) return 1;
    return 0;
}

int main()
{
    cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1, 0, 0);
    dfs2(1, 1);
    build(1, 1, n);
    while(m--)
    {
        int a, b;
        char c;
        cin >> a >> b >> c;
        cout << queryPath(a, b, c);
    }
    cout << endl;
    return 0;
}

2、聪明的做法:连通块+并查集

树上所有节点可以按照G或者H划分多个连通块,如果属于同一个连通块(状态相同),可以通过并查集对相邻节点进行合并;

对于每次询问a b c,只有a、b属于同一个连通块且状态与c不相同才能输出0,其他情况都输出1;

因为,只要不属于同一个连通块,路径中必然会有两种状态。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;

char s[N];
int p[N];
int n, m;

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

int main()
{
    cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
    {
        cin >> s[i];
        p[i] = i; //并查集初始化
    }
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        if(s[u] == s[v]) p[find(u)] = find(v);
    }
    while(m--)
    {
        int a, b;
        char c;
        cin >> a >> b >> c;
        if(find(a) == find(b) && s[a] != c) cout << 0;
        else cout << 1;
    }
    return 0;
}

 

posted @ 2025-03-18 11:31  hackerchef  阅读(31)  评论(0)    收藏  举报