BZOJ 5477: 星际穿越

当初随便出的一道 思博题 竟然被交换到了八中 QAQ

然后就上了 BZOJ 。。。作为原作者还是把原来写的详细题解放出来吧 qwq

题意

n 个点的数,每个点初始有权值 vi ,需要支持 m 次操作。

动态单点加权值,查询子树内点对的距离和。

题解

5pts

观察到第 9 个点,只有修改,没有询问。只需要正确打开文件并且不输出任何字符即可。

(注意暴力不能 RE 或者 MLETLE 才能 艰难地 拿到这个分)

15pts

对于 n,m50 的点,每次直接修改点权。

询问的时候暴力枚举子树中的点对,然后跳 Lca 计算距离。

复杂度是 O(mn3) 的。

30pts

发现暴力枚举点对的时候不需要重新跳 Lca ,直接从每个点 Bfs or Dfs 即可。

复杂度是 O(mn2) 的。

45pts

考虑把暴力这颗子树弄出来,就变成求树上两两点对带权距离和的经典问题。
如果工业一点就可以写个点分治,复杂度就是 O(mnlogn) 的,常数优秀应该可以得到这些分。

但其实没有这么麻烦,如果是边权的话,每个边被多少条路径经过,其实就是两边子树 size (子树大小)的乘积,也就是 size(totsize+1)

但这题权值是在点上的,所以要枚举它每一个儿子顺次计算,就是每个点的 size 和前面的 size 之和的乘积,最后加上子树内外的 size 之积就行了。然后复杂度就是 O(nm) 的。

还有一种一遍 dp 一遍换根的做法就不多赘述了,复杂度也是 O(nm) 的。

55pts

其实刚刚那个 O(nmlogn) 或者 O(nm) 的算法也可以通过 10,11 号点的...

60pts

但对于只有询问的点,应该是有更好的做法,利用两点距离 disa,b=depa+depbdeplca(a,b)×2+vlca(a,b) 这个经典计算方式。

考虑每个点的 dep 计算了多少次,以及它作为 lca 时计算了多少次 depv

我们推导式子:

ans=uchildpvchildp,vudepu+depvdeplca(a,b)2+vlca(a,b)=(uchildpdepu)×(sizep+1)+uchildp(vudepu2)coefu

此处 coefuu 作为 lca 出现的次数。至于求这个,可以依次考虑它的每个儿子,系数就是每个儿子的 sz 乘上前面所有 sz 的和(一开始要算上 u 点)。

我们用 Dfs 预处理就行了,记下当前的 uchildpdepu 的值,以及 uchildp(vudepu2)coefu 即可在 O(n) 的时间里预处理出所有点的答案。

80pts

ui=vi1 :直接考虑每个点被多少个区间覆盖即可,用线段树支持动态修改和查询。

ui=1 :分类讨论即可。询问的时候 p=2u1 的时候直接输出 vpu=1 的时候也可以十分轻易地直接维护答案。

这些点只是为了凑部分分的

100pts

方法一

至于正解,我们考虑动态维护前面 60pts 的式子。

我们发现每次只需要动态查询子树的 depu (带权深度)的和,以及 uchildp(vudepu2)coefu 就行了。

每次修改单点权值,等价于修改子树内所有点的带权深度和,我们用线段树支持子树修改就行了,然后询问的时候就子树询问。

至于  coefu ,我们对于线段树每个区间维护 coef 的和,每次给线段树区间加的时候,把 sum 加上 coef×x 就行了,复杂度就是 O(mlogn) 的。

方法二

考虑前面 45pts 其中的一种做法,考虑一个点被多少条路径穿过。

我们先假设只询问全树,那么可以用树状数组维护每个点的系数。那么就可以直接单点修改,区间查询就行了。

如果是询问子树的话,也是很简单的,我们减去经过子树 u 内点的路径的多余贡献就行了。具体来说,就是子树 u 中每个点的 size 乘上子树 u 外的点数 nsizeu 就行了。

同样这个也可以用树状数组实现,十分的好写好调。

一些有意思的东西

这题应该是一道原创 送分 题,其实思路十分的简单,体现了出题人的良心。

考察了对于代码的实现以及对于数据结构的实现。

就是代码实现略微有点细节,利用了一个差分的小 trick

在出完这道题后,找 Hometown 验题的时候,他告诉我了方法二,简单好写常数小,发现 std 又双叒叕被踩了。。。 果然我只能出思博题啊!

代码

方法一

这是出题人一开始想到的一个 sb 方法,常数大,还难写。。

/************************************************************** Problem: 5477 User: zjp_shadow Language: C++ Result: Accepted Time:8816 ms Memory:67256 kb ****************************************************************/ #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 () { //freopen ("transport.in", "r", stdin); //freopen ("transport.out", "w", stdout); 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 () { ////freopen ("transport.in", "r", stdin); ////freopen ("transport.out", "w", stdout); 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__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10374574.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(604)  评论(4编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 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】
点击右上角即可分享
微信分享提示