子树求和问题通常有三种解法。
像求子树 size 那样,可以用直接的子树合并来解决的问题,大家想必都会了。
我们来介绍一个技巧,可以把对路径的询问或操作,转化为子树求和问题。
给你一个
有
求
把点
我们可以把点
也就是说,有一个序列
把从
我们把这个技巧称为树上差分。
在上述问题中,如果把点权换成边权。把操作改成
又该如何处理呢?
const int maxn = 5e4 + 5;
vector<int> g[maxn];
int num, L[maxn], R[maxn];
bool is_ancestor(int a, int b) { // a 是不是 b 的祖先
return L[a] <= L[b] && L[b] <= R[a];
}
int anc[maxn][16];
int lca(int u, int v) {
if (is_ancestor(u, v)) return u;
if (is_ancestor(v, u)) return v;
for (int i = 15; i >= 0; i--) {
if (anc[u][i] && !is_ancestor(anc[u][i], v))
u = anc[u][i];
}
return anc[u][0];
}
void dfs(int u, int p) {
L[u] = ++num;
anc[u][0] = p;
for (int i = 1; i < 16; i++)
anc[u][i] = anc[anc[u][i - 1]][i - 1];
for (int v : g[u])
if (v != p) {
dfs(v, u);
}
R[u] = num;
}
int a[maxn];
void get_sum(int u, int p) { // 求子树和
for (int v : g[u])
if (v != p) {
get_sum(v, u);
a[u] += a[v];
}
}
int main() {
int n, k; cin >> n >> k;
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
while (k--) {
int s, t; cin >> s >> t;
int u = lca(s, t);
a[s]++; a[t]++;
a[u]--; a[anc[u][0]]--;
}
get_sum(1, 0);
cout << *max_element(a + 1, a + n + 1) << '\n';
}
给你一个有
第
有
你可以选一条边把它改造成虫洞,走过虫洞花费的时间是
所有运输计划同时开始。求完成全部计划最少要花多少时间。
枚举边
给定总时间的上限
考虑原本总时间大于
问题化为
每条边有一个权值,最初都是
long long dist[maxn]; // dist[i]:根到点i的距离
int we[maxn]; //we[i]:点i的父边的长度。
vector<int> postorder; //用非递归的方式计算子树和
void dfs(int u, int p) {
L[u] = ++num;
anc[u][0] = p;
for (int i = 1; i < 19; i++)
anc[u][i] = anc[anc[u][i - 1]][i - 1];
for (auto [v, w] : g[u])
if (v != p) {
dist[v] = dist[u] + w;
dfs(v, u);
we[v] = w;
}
R[u] = num;
postorder.push_back(u);
}
int a[maxn];
int n, m;
int s[maxn], t[maxn], LCA[maxn];
long long len[maxn];
long long max_len;
bool check(long long k) {
memset(a, 0, sizeof a);
int cnt = 0;
for (int i = 0; i < m; i++)
if (len[i] > k) {
cnt++;
// 把 s[i] 到 t[i] 的路径上的边值加 1
a[s[i]]++; a[t[i]]++;
a[LCA[i]] -= 2;
}
//计算子树和
for (int v : postorder)
a[anc[v][0]] += a[v];
for (int i = 1; i <= n; i++)
if (a[i] == cnt && max_len - we[i] <= k)
return true;
return false;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs(1, 0);
for (int i = 0; i < m; i++) {
cin >> s[i] >> t[i];
LCA[i] = lca(s[i], t[i]);
len[i] = dist[s[i]] + dist[t[i]] - 2 * dist[LCA[i]];
}
max_len = *max_element(len, len + m);
long long ok = max_len, ng = -1;
while (ok - ng > 1) {
long long k = (ok + ng) / 2;
if (check(k))
ok = k;
else
ng = k;
}
cout << ok << '\n';
}
前缀:有根树的先序遍历所得序列的前缀。
给你一个有
点
回答
在本题里,约定根的深度为
在 DFS 的过程中,我们维护
为了回答询问
若结果中 1 不超过
const int maxn = 5e5 + 5;
vector<int> g[maxn];
char c[maxn];
vector<pair<int,int>> query[maxn];
bitset<26> p[maxn]; //全局数据结构
bitset<26> ans[maxn];
void dfs(int u, int depth) {
for (auto [h, id] : query[u])
ans[id] = a[h];
p[depth].flip(c[u] - 'a');
for (int v : g[u])
dfs(v, depth + 1);
for (auto [h, id] : query[u])
ans[id] ^= p[h];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int n, m; cin >> n >> m;
for (int i = 2; i <= n; i++) {
int p; cin >> p;
g[p].push_back(i);
}
for (int i = 1; i <= n; i++)
cin >> c[i];
for (int i = 0; i < m; i++) {
int v, h; cin >> v >> h;
query[v].push_back({h, i});
}
dfs(1, 1);
for (int i = 0; i < m; i++)
cout << (ans[i].count() < 2 ? "Yes" : "No") << '\n';
}
上面的解法是离线的,时间
还有一种类似的在线解法。
对每个
对每个
对于询问
bitset<26> s[maxn];
vector<int> order[maxn];
int L[maxn], R[maxn], num;
void dfs(int u, int depth) {
L[u] = ++num;
int prev = order[depth].empty() ? 0 : order[depth].back();
s[num] = s[prev];
s[num].flip(c[u] - 'a');
order[depth].push_back(num);
for (int v : g[u])
dfs(v, depth + 1);
R[u] = num;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
// 输入 ...
dfs(1, 1);
for (int i = 0; i < m; i++) {
int v, h; cin >> v >> h;
bitset<26> ans;
auto l = lower_bound(order[h].begin(), order[h].end(), L[v]);
auto r = upper_bound(order[h].begin(), order[h].end(), R[v]);
for (auto ptr : {l, r})
if (ptr != order[h].begin())
ans ^= s[*(ptr - 1)];
cout << (ans.count() > 1 ? "No" : "Yes") << '\n';
}
}
离线解法更好写。
有一个有
每天有
在每个点上都有一个观察员。一个人能被点
求每个观察员会观察到多少人?
注意:设某人的终点是
考虑以点
把点
设某人从
每个观察员会观察到多少个上行的人?
某人从
即
我们可以这样看
观察员
每个观察员会观察到多少个下行的人?
某人从
即
我们可以这样看
观察员
用树上差分来处理这里的「向一条路径上的每个点的集合里添加同一个数」的操作。
这里的路径是特殊的:两个端点是“祖先—后代”关系。
const int maxn = 3e5 + 5;
int num, L[maxn], R[maxn];
bool is_ancestor(int a, int b) { // a 是不是 b 的祖先
return L[a] <= L[b] && L[b] <= R[a];
}
int anc[maxn][19];
int lca(int u, int v) {
if (is_ancestor(u, v)) return u;
if (is_ancestor(v, u)) return v;
for (int i = 18; i >= 0; i--) {
if (anc[u][i] && !is_ancestor(anc[u][i], v))
u = anc[u][i];
}
return anc[u][0];
}
vector<int> g[maxn];
int depth[maxn];
void dfs(int u, int p) {
L[u] = ++num;
anc[u][0] = p;
depth[u] = depth[p] + 1;
for (int i = 1; i < 19; i++)
anc[u][i] = anc[anc[u][i - 1]][i - 1];
for (int v : g[u])
if (v != p) {
dfs(v, u);
}
R[u] = num;
}
int cnt[2 * maxn];
int ans[maxn];
vector<pair<int,int>> op[maxn];
void get_up(int u, int p) {
int key = w[u] + depth[u];
int before = cnt[key];
for (auto [k, type] : op[u])
cnt[k] += type;
for (int v : g[u])
if (v != p)
get_up(v, u);
ans[u] += cnt[key] - before;
}
int n, m;
void get_down(int u, int p) {
int key = w[u] - depth[u] + n;
int before = cnt[key];
for (auto [k, type] : op[u]) {
cnt[k] += type;
}
for (int v : g[u])
if (v != p)
get_down(v, u);
ans[u] += cnt[key] - before;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= n; i++)
cin >> w[i];
dfs(1, 0);
vector<int> s(m), t(m), LCA(m);
for (int i = 0; i < m; i++) {
cin >> s[i] >> t[i];
LCA[i] = lca(s[i], t[i]);
ans[LCA[i]] += depth[s[i]] == depth[LCA[i]] + w[LCA[i]];
}
for (int i = 0; i < m; i++) {
int k = depth[s[i]];
op[s[i]].push_back({k, 1});
op[LCA[i]].push_back({k, -1});
}
get_up(1, 0);
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; i++)
op[i].clear();
for (int i = 0; i < m; i++) {
int time = depth[s[i]] + depth[t[i]] - 2 * depth[LCA[i]];
int k = time - depth[t[i]] + n;
op[t[i]].push_back({k, 1});
op[LCA[i]].push_back({k, -1});
}
get_down(1, 0);
for (int i = 1; i <= n; i++)
cout << ans[i] << ' ';
cout << '\n';
}