当初随便出的一道 思博题 竟然被交换到了八中 QAQ
然后就上了 BZOJ 。。。作为原作者还是把原来写的详细题解放出来吧 qwq
题意
n 个点的数,每个点初始有权值 vi ,需要支持 m 次操作。
动态单点加权值,查询子树内点对的距离和。
题解
5pts
观察到第 9 个点,只有修改,没有询问。只需要正确打开文件并且不输出任何字符即可。
(注意暴力不能 RE 或者 MLE 与 TLE 才能 艰难地 拿到这个分)
15pts
对于 n,m≤50 的点,每次直接修改点权。
询问的时候暴力枚举子树中的点对,然后跳 Lca 计算距离。
复杂度是 O(mn3) 的。
30pts
发现暴力枚举点对的时候不需要重新跳 Lca ,直接从每个点 Bfs or Dfs 即可。
复杂度是 O(mn2) 的。
45pts
考虑把暴力这颗子树弄出来,就变成求树上两两点对带权距离和的经典问题。
如果工业一点就可以写个点分治,复杂度就是 O(mnlogn) 的,常数优秀应该可以得到这些分。
但其实没有这么麻烦,如果是边权的话,每个边被多少条路径经过,其实就是两边子树 size (子树大小)的乘积,也就是 size∗(tot−size+1) 。
但这题权值是在点上的,所以要枚举它每一个儿子顺次计算,就是每个点的 size 和前面的 size 之和的乘积,最后加上子树内外的 size 之积就行了。然后复杂度就是 O(nm) 的。
还有一种一遍 dp 一遍换根的做法就不多赘述了,复杂度也是 O(nm) 的。
55pts
其实刚刚那个 O(nmlogn) 或者 O(nm) 的算法也可以通过 10,11 号点的...
60pts
但对于只有询问的点,应该是有更好的做法,利用两点距离 disa,b=depa+depb−deplca(a,b)×2+vlca(a,b) 这个经典计算方式。
考虑每个点的 dep 计算了多少次,以及它作为 lca 时计算了多少次 dep 与 v 。
我们推导式子:
ans=∑u∈childp∑v∈childp,v≥udepu+depv−deplca(a,b)∗2+vlca(a,b)=(∑u∈childpdepu)×(sizep+1)+∑u∈childp(vu−depu∗2)∗coefu
此处 coefu 为 u 作为 lca 出现的次数。至于求这个,可以依次考虑它的每个儿子,系数就是每个儿子的 sz 乘上前面所有 sz 的和(一开始要算上 u 点)。
我们用 Dfs 预处理就行了,记下当前的 ∑u∈childpdepu 的值,以及 ∑u∈childp(vu−depu∗2)∗coefu 即可在 O(n) 的时间里预处理出所有点的答案。
80pts
ui=vi−1 :直接考虑每个点被多少个区间覆盖即可,用线段树支持动态修改和查询。
ui=1 :分类讨论即可。询问的时候 p=2 ,u≠1 的时候直接输出 vp ,u=1 的时候也可以十分轻易地直接维护答案。
这些点只是为了凑部分分的。
100pts
方法一
至于正解,我们考虑动态维护前面 60pts 的式子。
我们发现每次只需要动态查询子树的 ∑depu (带权深度)的和,以及 ∑u∈childp(vu−depu∗2)∗coefu 就行了。
每次修改单点权值,等价于修改子树内所有点的带权深度和,我们用线段树支持子树修改就行了,然后询问的时候就子树询问。
至于 ∗ coefu ,我们对于线段树每个区间维护 coef 的和,每次给线段树区间加的时候,把 sum 加上 coef×x 就行了,复杂度就是 O(mlogn) 的。
方法二
考虑前面 45pts 其中的一种做法,考虑一个点被多少条路径穿过。
我们先假设只询问全树,那么可以用树状数组维护每个点的系数。那么就可以直接单点修改,区间查询就行了。
如果是询问子树的话,也是很简单的,我们减去经过子树 u 内点的路径的多余贡献就行了。具体来说,就是子树 u 中每个点的 size 乘上子树 u 外的点数 n−sizeu 就行了。
同样这个也可以用树状数组实现,十分的好写好调。
一些有意思的东西
这题应该是一道原创 送分 题,其实思路十分的简单,体现了出题人的良心。
考察了对于代码的实现以及对于数据结构的实现。
就是代码实现略微有点细节,利用了一个差分的小 trick 。
在出完这道题后,找 Hometown 验题的时候,他告诉我了方法二,简单好写常数小,发现 std 又双叒叕被踩了。。。 果然我只能出思博题啊!
代码
方法一
这是出题人一开始想到的一个 sb 方法,常数大,还难写。。
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define pb push_back
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
typedef long long ll;
const int N = 3e5 + 1e3, Mod = 1e9 + 7;
vector<int> G[N]; int id[N];
inline int Plus(int a, int b) {
return (a += b) >= Mod ? a - Mod : a;
}
#define lson o << 1, l, mid
#define rson o << 1 | 1, mid + 1, r
template<int Maxn>
struct Segment_Tree {
int coef[Maxn], sumv[Maxn], tag[Maxn];
inline void Modify(int o, int uv) {
tag[o] = Plus(tag[o], uv);
sumv[o] = (sumv[o] + 1ll * coef[o] * uv) % Mod;
}
inline void Push_Down(int o) {
if (tag[o])
Modify(o << 1, tag[o]), Modify(o << 1 | 1, tag[o]), tag[o] = 0;
}
inline void Push_Up(int o) {
sumv[o] = Plus(sumv[o << 1], sumv[o << 1 | 1]);
}
void Build(int o, int l, int r, int *base, int *cur) {
if (l == r) { sumv[o] = 1ll * (coef[o] = base[id[l]]) * cur[id[l]] % Mod; return ; }
int mid = (l + r) >> 1;
Build(lson, base, cur); Build(rson, base, cur);
Push_Up(o); coef[o] = Plus(coef[o << 1], coef[o << 1 | 1]);
}
void Update(int o, int l, int r, int ul, int ur, int uv) {
if (ul <= l && r <= ur) { Modify(o, uv); return ; }
int mid = (l + r) >> 1; Push_Down(o);
if (ul <= mid) Update(lson, ul, ur, uv);
if (ur > mid) Update(rson, ul, ur, uv); Push_Up(o);
}
int Query(int o, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return sumv[o];
int mid = (l + r) >> 1, res = 0; Push_Down(o);
if (ql <= mid) res = Plus(res, Query(lson, ql, qr));
if (qr > mid) res = Plus(res, Query(rson, ql, qr));
Push_Up(o); return res;
}
};
#undef lson
#undef rson
Segment_Tree<N << 2> T1, T2, T3;
int coef[N], dfn[N], efn[N], sz[N], dep[N];
void Dfs_Init(int u = 1, int fa = 0) {
static int clk = 0;
dep[u] = dep[fa] + 1;
id[dfn[u] = ++ clk] = u;
coef[u] = sz[u] = 1;
Rep (i, G[u].size()) {
int v = G[u][i];
if (v != fa) {
Dfs_Init(v, u);
coef[u] = (coef[u] + 1ll * sz[u] * sz[v]) % Mod;
sz[u] += sz[v];
}
}
efn[u] = clk;
}
int main () {
int n = read(), m = read();
For (i, 1, n - 1) {
int u = read(), v = read();
G[u].pb(v); G[v].pb(u);
}
Dfs_Init();
int I[N]; For (i, 1, n) I[i] = 1;
T1.Build(1, 1, n, I, dep);
T2.Build(1, 1, n, coef, dep);
T3.Build(1, 1, n, coef, I);
For (i, 1, m) {
int opt = read(), pos = read();
if (opt == 1) {
int val = read();
T1.Update(1, 1, n, dfn[pos], efn[pos], val);
T2.Update(1, 1, n, dfn[pos], efn[pos], val);
T3.Update(1, 1, n, dfn[pos], dfn[pos], val);
} else {
long long ans =
1ll * T1.Query(1, 1, n, dfn[pos], efn[pos]) * (sz[pos] + 1)
- T2.Query(1, 1, n, dfn[pos], efn[pos]) * 2ll
+ T3.Query(1, 1, n, dfn[pos], efn[pos]);
printf ("%lld\n", (ans % Mod + Mod) % Mod);
}
}
return 0;
}
方法二
简单的树状数组解法 QAQ
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define pb push_back
using namespace std;
typedef long long ll;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
const int N = 3e5 + 1e3, Mod = 1e9 + 7;
vector<int> G[N];
int n, dfn[N], efn[N], sz[N], coef[N];
inline int Plus(int a, int b) {
return (a += b) >= Mod ? a - Mod : a;
}
#define lowbit(x) (x & -x)
template<int Maxn>
struct Fenwick_Tree {
int sumv[Maxn];
inline void Update(int pos, int uv) {
for (; pos <= n; pos += lowbit(pos))
sumv[pos] = Plus(sumv[pos], uv);
}
inline int Query(int pos) {
int res = 0;
for (; pos; pos -= lowbit(pos))
res = Plus(res, sumv[pos]);
return res;
}
inline int Query(int l, int r) {
return Query(r) - Query(l - 1);
}
};
Fenwick_Tree<N> T1, T2;
void Dfs_Init(int u = 1, int fa = 0) {
static int clk = 0;
dfn[u] = ++ clk;
sz[u] = coef[u] = 1;
Rep (i, G[u].size()) {
int v = G[u][i];
if (v != fa) {
Dfs_Init(v, u);
coef[u] = (coef[u] + 1ll * sz[u] * sz[v]) % Mod;
sz[u] += sz[v];
}
}
coef[u] = (coef[u] + 1ll * sz[u] * (n - sz[u])) % Mod;
T1.Update(dfn[u], coef[u]);
T2.Update(dfn[u], sz[u]); efn[u] = clk;
}
int main () {
n = read(); int m = read();
For (i, 1, n - 1) {
int u = read(), v = read();
G[u].pb(v); G[v].pb(u);
}
Dfs_Init();
For (i, 1, m) {
int opt = read(), pos = read();
if (opt == 1) {
int val = read();
T1.Update(dfn[pos], 1ll * val * coef[pos] % Mod);
T2.Update(dfn[pos], 1ll * val * sz[pos] % Mod);
} else {
long long ans =
T1.Query(dfn[pos], efn[pos])
- 1ll * T2.Query(dfn[pos], efn[pos]) * (n - sz[pos]);
printf ("%lld\n", (ans % Mod + Mod) % Mod);
}
}
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】