原题链接: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;
}