树上dfs序差分+树上桶差分
如果对于一个节点p,如果子树内的点对自身有贡献,就可以在递归子树时用桶和差分来维护答案。
以这道题目为例:https://www.luogu.com.cn/problem/P1600
这个博主提供了详细题解很强:https://www.cnblogs.com/lfyzoi/p/10221884.html
总结下来有几个注意要点:
- 差分就是对于节点i加上贡献,在lca的父节点再把贡献删除,这样就维护了一条链上的贡献
- 要点:递归子树时,子节点v1产生的贡献不应该算给v2,但是不加处理的话会算进去。所以在递归v2开始时记录要用的桶的值然后算完v2减去记录的值就是答案。(因为每个点用的桶很少所以可实现)
- 桶要开两个,不然会算重复(因为s和t答案贡献是分开的
- s,t对他们的lca产生贡献时会加两次,所以要特判在ans[lca]处直接减掉
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
const int N = 3e5 + 1;
vector<int> E[N];
int n, m, tot, a[N], rev[N], top[N], id[N];
int siz[N], dep[N], fa[N], hson[N];
int w[N], bu1[N*2], bu2[N*2], ans[N];
vector<pair<int, int>> xg1[N], xg2[N];
void dfs1 (int u) {
siz[u] = 1;
dep[u] = dep[fa[u]] + 1;
for (auto v : E[u]) {
if (v == fa[u]) continue;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (siz[v] > siz[hson[u]]) hson[u] = v;
}
}
void dfs2(int u, int topu) {
rev[id[u] = ++tot] = u;
top[u] = topu;
if (hson[u]) dfs2(hson[u], topu);
for (int v : E[u]) {
if (v == fa[u] || v == hson[u]) continue;
dfs2(v, v);
}
}
int lca(int u, int v) {
while(top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) swap(u, v);
v = fa[top[v]];
}
if (dep[u] > dep[v]) swap(u, v);
return u;
}
void dfs(int x, int gs) {
for (int v : E[x]) {
if (v == fa[x]) continue;
dfs(v, bu2[w[v] - dep[v] + n] + bu1[dep[v] + w[v]]);
}
for (auto j : xg1[x]) {
bu1[j.first] += j.second;
}for (auto j : xg2[x]) {
bu2[j.first] += j.second;
}
ans[x] += bu1[dep[x] + w[x]] - gs + bu2[n + w[x] - dep[x]];
}
void solve() {
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
E[u].push_back(v), E[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
dfs1(1);
dfs2(1, 1);
for (int i = 1; i <= m; i++) {
int s, t;
cin >> s >> t;
int LCA = lca(s, t);
int dis = dep[s] + dep[t] - 2 * dep[LCA];
xg2[t].push_back({dis - dep[t] + n, 1});
xg2[fa[LCA]].push_back({dis - dep[t] + n, -1});
xg1[s].push_back({dep[s], 1});
xg1[fa[LCA]].push_back({dep[s], -1});
if (dep[LCA] + w[LCA] == dep[s]) ans[LCA]--;
}
dfs(1, 0);
for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
}
dfs序+树状数组差分
题目:https://www.luogu.com.cn/problem/P4219
这个比上面内容好理解一点,就是子树都是连续的编号然后用树状数组求区间和,对于时序更新问题常常离线预排序处理掉
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
#define endl '\n'
#define ll long long
#define pii pair<int, int>
pii q[N];
bool que[N];
int n, m, ld[N], rd[N], tot, fa[N], dep[N], siz[N];
int t[N];
vector<int> E[N];
void dfs(int x) {
ld[x] = ++tot;
dep[x] = dep[fa[x]] + 1;
siz[x] = 1;
for (int i : E[x]) {
if (i == fa[x]) continue;
fa[i] = x;
dfs(i);
siz[x] += siz[i];
}
rd[x] = tot;
}
struct unifind{
int fa[N];
void init() {for (int i = 1; i <= n; i++) fa[i] = i;}
int find(int x) {
return (x == fa[x]) ? (x) : fa[x] = find(fa[x]);}
}uni;
void upd(int x, int k) {
if (x == 0) return;
while(x < N) {
t[x] += k;
x += (x & -x);
}
}
int tque(int x) {
int res = 0;
while(x) {
res += t[x];
x -= (x & -x);
}
return res;
}
int pque(int l, int r) {
return tque(r) - tque(l - 1);
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
char c;
int x, y;
cin >> c >> x >> y;
q[i] = {x, y};
if (c == 'Q')
que[i] = 1;
else
E[x].push_back(y), E[y].push_back(x);
}
for (int i = 1; i <= n; i++) {
if (!ld[i]) dfs(i);
}
uni.init();
for (int i =1; i <= n; i++) {
upd(ld[i], 1); upd(ld[fa[i]], -1);
}
for (int i = 1; i <= m; i++) {
int x = q[i].first, y = q[i].second;
if (ld[x] < ld[y]) swap(x, y);
int fy = uni.find(y);
// cout << ld[x] << ' ' << ld[y] <<"xxx" <<endl;
int sizx = pque(ld[x], rd[x]), sizfy = pque(ld[fy], rd[fy]);
// cout << "YYY" << endl;
if (!que[i]) {
uni.fa[x] = fy;
upd(ld[y], sizx); upd(ld[fa[fy]], -sizx);
}
else {
cout << 1ll * sizx * (-1ll*sizx + 1ll*sizfy) << endl;
}
}
}

浙公网安备 33010602011771号