P1600题解
来一发线段树合并的题解。
脑子不够,数据结构来凑
这题到现在是不是只有我一个人拿线段树合并过了?
树上路径的问题大多都要转化为
由于增加贡献的类型是动态变化的,我们不能用暴力数据结构(线段树:你***)这样的东西直接区间处理,而是要转化为树上差分来解决。
具体地,考虑对每一个观察哨进行处理,对于每一个观察哨我们开一个桶,下面我们进行分类
(下面的式子是好得出的,理解不了建议自己画图理解)。
对于每个在路径
- 若
在 上:那么
对于这种情况,我们差分时每次给桶里增加的值是
- 若
在 上,那么
对于这种情况,我们差分时每次给桶里增加的值是
啊注意这样的话值域可能为负(即
然后怎么统计答案?
这是桶玩家最难受的地方,但是线段树合并玩家最高兴的地方
一般地,刚才的桶我们要对每个节点开
但是我们发现这个桶的本质维护的是值域,且一个点的贡献只可能在它的子树中,且子树中所有满足条件的点都会成为它的贡献。
看到值域,看到答案中底层对顶层的后效性,这TM不就是线段树合并裸题吗?
统计答案时
灰常的暴力,但时间复杂度灰常优秀的
上代码,不懂看注释。
//P1600 [NOIP2016] 天天爱跑步
//脑子不够数据结构来凑
//对于每个节点建一棵权值线段树,略微LCA+差分一下即可
//很暴力的思想,暴力实现即可
//然后? 没了。
#include <bits/stdc++.h>
#define N 300001
#define M 20
using namespace std;
int n, m;
//链式前向星存图
struct Edge {
int to, nxt;
} e[N << 1];
int head[N], cnt;
void add(int u, int v) {
e[++cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt;
}
int dep[N];
//倍增LCA用的数组,即每个节点x的2^i个祖先
int anc[N][M];
void dfs(int x, int fa, int d) {
dep[x] = d;
anc[x][0] = fa;
for (int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if (y == fa)
continue;
dfs(y, x, d + 1);
}
}
void init() {
anc[1][0] = -1;
dfs(1, -1, 1);
//正常递推
for (int j = 1; j < M; j++)
for (int i = 1; i <= n; i++)
anc[i][j] = anc[anc[i][j - 1]][j - 1];
}
//LCA,不懂建议自行复习
int LCA(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int i = M - 1; i >= 0; i--)
if (dep[x] - (1 << i) >= dep[y])
x = anc[x][i];
if (x == y)
return x;
for (int i = M - 1; i >= 0; i--)
if (anc[x][i] != anc[y][i]) {
x = anc[x][i];
y = anc[y][i];
}
return anc[x][0];
}
//动态开点线段树,不会的建议自行学习
struct Node {
int lc, rc;
int sum;
} t[N * 60]; // 开 2Nlog(2N)
#define lc(i) t[i].lc
#define rc(i) t[i].rc
#define sum(i) t[i].sum
#define mid ((l + r) >> 1)//我就是懒好吧
int tot = 1;
void push_up(int p) {
sum(p) = sum(lc(p)) + sum(rc(p));
}
void point_add(int &p, int l, int r, int x, int val) {
if (!p)
p = ++tot;
if (l == r) {
sum(p) += val;
return;
}
if (x <= mid)
point_add(lc(p), l, mid, x, val);
else
point_add(rc(p), mid + 1, r, x, val);
push_up(p);
return;
}
int query(int p, int l, int r, int x) {
if (!p)
return 0;
if (l == r)
return sum(p);
if (x <= mid)
return query(lc(p), l, mid, x);
else
return query(rc(p), mid + 1, r, x);
}
//也不知道为啥,这里传参必须传(p, q, l, r)四个参数,只传(p, q)过不了第二个样例qwq
int Merge(int p, int q, int l, int r) {
if (!p || !q)
return p + q;
if (l == r) {
sum(p) += sum(q);
return p;
} else
lc(p) = Merge(lc(p), lc(q), l, mid);
rc(p) = Merge(rc(p), rc(q), mid + 1, r);
return p;
}
int w[N];
int ans[N];
int rt[N];//存每一个点在线段树里的根节点编号
void join(int x) {
for (int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if (y == anc[x][0])
continue;
join(y);
rt[x] = Merge(rt[x], rt[y], 1, (n << 1));
}
if (w[x] && n + dep[x] + w[x] <= 2 * n)//不能越界
ans[x] += query(rt[x], 1, (n << 1), n + dep[x] + w[x]);
ans[x] += query(rt[x], 1, (n << 1), n + dep[x] - w[x]);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
init();
for (int i = 1; i <= n; i++)
scanf("%d", &w[i]);
while (m--) {
int s, t;
scanf("%d%d", &s, &t);
int lca = LCA(s, t);
//平移了值域
point_add(rt[s], 1, (n << 1), n + dep[s], 1);
point_add(rt[t], 1, (n << 1), n + dep[lca] * 2 - dep[s], 1);
point_add(rt[lca], 1, (n << 1), n + dep[s], -1);//差分
point_add(rt[anc[lca][0]], 1, (n << 1), n + dep[lca] * 2 - dep[s], -1);
}
join(1);
for (int i = 1; i <= n; i++)
cout << ans[i] << " ";
puts("");
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战