洛谷题单指南-图论之树-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;
}
浙公网安备 33010602011771号