BZOJ 3653: 谈笑风生(离线, 长链剖分, 后缀和)

题意

给你一棵有 n 个点并且以 1 为根的树。共有 q 次询问,每次询问两个参数 p,k 。询问有多少对点 (p,a,b) 满足 p,a,b 为三个不同的点,p,a 都为 b 的祖先,且 pa 的距离不能超过 k

n300000,q300000 不要求强制在线。

题解

dep[u] 为点 u 的深度,sz[u]u 的子树大小(除去 u 本身)

首先我们考虑两种情况:

  1. ap 的祖先,那么这部分贡献很好计算,就是 min{dep[p]1,k}×sz[u]
  2. ap 的子树内,那么这部分贡献就是 dis(p,a)ksz[a]

我们现在只要考虑第二部分贡献怎么求。

不难发现,这些点的深度就是 [dep[p],dep[p]+k] 这个范围内的。

那么我们可以对于每个点用个 主席树 来存储这些信息,可以在线回答询问。

那么离线的话,可以考虑用 线段树合并 维护它每个子树的信息。

具体来说,这些都是对于每个 dep 维护它的 sz 的和,然后查区间和就行了。

然而这些时空复杂度都是 O(nlogn) ,其实还有更好的做法。

为什么我发现了呢qwq?

因为 fatesky 做这道题线段树合并做法的时候,Wearry 说可以 长链剖分 那就是 O(n) 的啦。

我们令 maxdep[u]=maxvchild[u]{dep[v} 也就是它子树中的最大深度。

具体来说,长链剖分就是把每个点儿子中 maxdep 最大的那个当做重儿子。重儿子与父亲连的边叫做重边。一连串重边不间断连到一起就叫做重链。

然后我们就有一条性质。

性质1 : 重链长度之和是 O(n) 的。

这个很显然啦,因为总共只有 O(n) 级别的边。

有了这个我们就可以解决一系列 关于深度的动态规划 问题了,对于这列问题常常都可以做到 O(n) 的复杂度。

具体操作就是,每次暴力继承重儿子的 dp 状态,然后轻儿子暴力合并上去。

不难发现这个复杂度是 O( 重链长 ) =O(n) 的。

继承的时候常常需要移位,并且把当前节点贡献算入,并且这个 dp 需要动态空间才能实现。

对于这道题我们考虑维护一个后缀和,也就是对于 u 子树中的 vdep[v]k 的所有 sz[v] 的和。

不难发现后缀和是很好合并的,这个的复杂度只需要 O(minmaxdep[v])

每次添加一个点 sz[u] 对于 dep[u] 的贡献只会对一个点的贡献产生影响,这个复杂度是 O(1) 的。

代码实现的话,就可以用一个 std :: vector ,按深度从大到小 ( maxdep[u]dep[u] )存储每个点的信息,因为这样最方便继承重儿子状态(每次加入状态只在整个 vector 末端添加一个元素)

其实可以动态开内存,顺着做,但我似乎学不来

常数似乎有点大,没比 O(nlogn) 快多少,vector 用多了... Wearry 到是优化了点常数到了 4000+ms

话说这个很像原来 DOFY 讲过的那道 Dsu on Tree

代码

#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 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 DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef long long ll; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("3653.in", "r", stdin); freopen ("3653.out", "w", stdout); #endif } const int N = 3e5 + 1e3; struct Ask { int k, id; } ; vector<Ask> V[N]; vector<int> G[N]; int sz[N], maxdep[N], dep[N], sonmaxdep[N], son[N], rt[N]; vector<ll> sum[N]; int n, q; ll ans[N], Size = 0; void Dfs_Init(int u, int fa = 0) { maxdep[u] = dep[u] = dep[fa] + 1; For (i, 0, G[u].size() - 1) { register int v = G[u][i]; if (v ^ fa) Dfs_Init(v, u), chkmax(maxdep[u], maxdep[v]); } } void Dfs(int u, int fa = 0) { For (i, 0, G[u].size() - 1) { int v = G[u][i]; if (v == fa) continue ; Dfs(v, u); sz[u] += sz[v]; if (maxdep[v] > maxdep[son[u]]) son[u] = v; } rt[u] = rt[son[u]]; if (!rt[u]) rt[u] = ++ Size; int len = (int)sum[rt[u]].size(); ll Last = len ? sum[rt[u]][len - 1] : 0; sum[rt[u]].push_back(Last); if (son[u]) { For (i, 0, G[u].size() - 1) { int v = G[u][i]; if (v == fa || v == son[u]) continue ; For (j, 0, sum[rt[v]].size() - 1) { int nowdep = (maxdep[son[u]] - maxdep[v]) + j; sum[rt[u]][nowdep] += sum[rt[v]][j]; } sum[rt[u]][len] += sum[rt[v]][sum[rt[v]].size() - 1]; } } For (i, 0, V[u].size() - 1) { Ask now = V[u][i]; ans[now.id] = sum[rt[u]][len]; if (len > now.k) ans[now.id] -= sum[rt[u]][len - now.k - 1]; ans[now.id] += 1ll * min(dep[u] - 1, now.k) * sz[u]; } sum[rt[u]][len] += sz[u]; ++ sz[u]; } int main () { File(); n = read(); q = read(); For (i, 1, n - 1) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } For (i, 1, q) { int p = read(), k = read(); V[p].push_back((Ask) {k, i}); } Dfs_Init(1); Dfs(1); For (i, 1, q) printf ("%lld\n", ans[i]); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9262234.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(627)  评论(3编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示