P6626 [省选联考 2020 B 卷] 消息传递
P6626 [省选联考 2020 B 卷] 消息传递
题目描述
时间限制 | 空间限制
给定一个包含 个人(从 到 编号)的树形社交网络。如果一个人在某天收到了一条消息,则下一天他会将消息传递给所有与他有直接社交关系的人。
现在有 次询问,每次询问假定第 天 号人收到了一条消息,请你计算第 天时新收到此条消息的人数(即第 天前收到过此条消息的人不计入其中)。不同询问间互不影响。
输入格式
本题包含多组测试数据。
第一行一个整数 ,为测试数据组数。
对于每组测试数据:
第一行两个数 分别表示树形社交网络的人数和询问的数量。
接下来 行,每行两个数 ,表示 号人和 号人之间有直接社交关系。保证输入的是树形社交网络。
接下来 行,每行两个数 ,意义见题目描述。
输出格式
对于每组测试数据:输出 行,每行一个数表示询问的答案。
样例 #1
样例输入 #1
1
4 2
1 2
2 3
3 4
1 1
2 2
样例输出 #1
1
1
提示
样例解释
第一个询问,第一天新收到消息的人只有 号。
第二个询问,第一天新收到消息的人有 、 号,第二天新收到消息的人有 号。
数据范围与约定
对于测试点 :。
对于测试点 :。
对于测试点 :。
对于测试点 :。
对于测试点 :。
对于所有测试点:。
3 题解
分治做法,但不是点分治。
考虑与 距离为 的点,贡献分为两个部分:在 子树内与在 子树外。
对于 子树内的部分,问题等价于查询 子树内深度为 的点的个数,也就是区间某数出现次数。
由于作者画功不好,只能通过定义阐述内容。
假设树为以 为根的有根树,将 到 的最短路径上除 外的所有点称为树杈。
将该最短路径上所有边去除后,将每个树杈 位于的联通块称为 的树链。
对于一个树链,将其树杈 及 所直接连接的所有边去除后,剩余的若干联通块统称为 的树枝,称其中直径最大的联通块的直径为该树链的最大深度。
对于每个树杈,我们称与其直接连接的树杈中深度较大的树杈为其前驱。(对于与 直接连接的树杈特别定义其前驱为 )
对于 子树外的部分,答案为所有树杈 的树链内深度为 的点的个数的和。
容易发现, 的树链其实就是 的子树去除 的前驱的子树,所以对于每个树杈 ,其贡献为 的子树内深度为 的点的个数减去 的前驱的子树内深度为 的点的个数,也就是区间某数出现次数。
已知区间某数出现次数存在 的可持久化块状树做法,即将线段树分为 叉后可持久化。
显然,此时复杂度的全部瓶颈在于枚举树杈,最劣复杂度为
对 进行根号分治,当 时,有贡献的树杈个数只有最多 个,直接暴力枚举即可,复杂度为 。
当 时,继续进行分治:树链的最大深度 的树杈和 的树杈。
容易发现,最大深度 的树杈中对答案有贡献的树杈只有 个,也就是深度在 中的树杈。
显然,这些树杈我们可以 枚举,这一部分的复杂度为 。
对于最大深度 的树杈,个数只有 个。
对于每个树杈 记录一个 ,表示为深度最大的满足 的树杈。查询时只需要从 出发,不停前往 并记录贡献即可。
显然,枚举到的 一定覆盖了所有最大深度 的树杈。
这一部分复杂度也是 。
然后就得到了一个 时间复杂度和 空间复杂度的算法(可持久化块状树空间复杂度为 .然而,这道题对空间比较严苛,实现不好的 空间复杂度不能通过。
于是我们考虑用 且线性空间的 代替可持久化块状树。这是在不采用 空间复杂度时查询较快的一种方法,因为二分的常数通常非常小。
由于分界点两侧都用到了这个查询,所以没有办法平衡复杂度。
容易发现,当 时 的复杂度很难通过 。
作者在多次提交后发现,将 和 调到非常小时程序效率有显著提高。
这是因为,由于数据中树的形态比较集中,大部分树链都非常大,向上跳的次数远小于 。
经过测试, 效率最佳。
注意到第一次根号分治本质上没有多少用处,但是可以有效帮助我们处理边界情况,降低思维难度。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
const int N = 1e5 + 10;
#define mp make_pair
#define pii pair<int, int>
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int T, tot, n, m, t, cnt;
int head[N], ver[N * 2], last[N * 2], f[N][30], siz[N], pre[N], l[N], r[N], dep[N];
int a[N], ans[N], vis[N], bac[N];
vector<int> v[N];
int g[N];
void add(int x, int y)
{
ver[++tot] = y;
last[tot] = head[x];
head[x] = tot;
}
int find(int x)
{
int S = siz[x];
for (int j = 20; j >= 0; j--)
if (f[x][j] && siz[f[x][j]] - S < t) x = f[x][j];
return x;
}
int find2(int x, int y)
{
int D = dep[x];
for (int j = 20; j >= 0; j--)
if (f[x][j] && D - dep[f[x][j]] < y) x = f[x][j];
return x;
}
void dfs(int x, int fa)
{
siz[x] = 1;
l[x] = ++cnt; bac[cnt] = x; dep[x] = dep[fa] + 1;
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (y == fa) continue;
f[y][0] = x;
for (int j = 1; j <= 20; j++) f[y][j] = f[f[y][j - 1]][j - 1];
dfs(y, x); siz[x] += siz[y];
}
r[x] = l[x] + siz[x] - 1;
}
void init()
{
tot = cnt = 0;
for (int i = 1; i <= n; i++) head[i] = 0;
for (int i = 1; i <= n; i++) for (int j = 0; j <= 20; j++) f[i][j] = 0;
for (int i = 1; i <= n; i++) a[i] = 0, ans[i] = 0, bac[i] = 0;
for (int i = 1; i <= n; i++) v[i].clear();
}
int query(int num, int l, int r, int d, int xs)
{
int L = lower_bound(v[d].begin(), v[d].end(), l) - v[d].begin(), R = upper_bound(v[d].begin(), v[d].end(), r) - v[d].begin() - 1;
return R - L + 1;
}
int ask(int x, int y, int z, int d, int num)
{
int cha = dep[y] - dep[x], res = 0;
res += query(num, l[x], r[x], d - cha + dep[x], 1);
if (z) res -= query(num, l[z], r[z], d - cha + dep[x], -1);
return res;
}
int main()
{
T = read();
while (T--)
{
n = read(), m = read();
init();
t = 30;
for (int i = 1; i < n; i++)
{
int u = read(), v = read();
add(u, v); add(v, u);
}
dfs(1, 0);
for (int i = 1; i <= n; i++) pre[i] = find(i);
for (int i = 1; i <= n; i++) v[dep[bac[i]]].push_back(i);
for (int i = 1; i <= m; i++)
{
int x = read(), d = read(), res = 0;
res += ask(x, x, 0, d, i);
if (d <= t)
{
int cur = x;
for (int j = 1; j <= t; j++)
{
if (cur == 1) break;
res += ask(f[cur][0], x, cur, d, i);
cur = f[cur][0];
}
}
else
{
int cur = find2(x, d - t), cntt = 0;
while (cur != 1 && dep[x] - dep[cur] <= d)
{
vis[f[cur][0]] = 1; g[++cntt] = f[cur][0];
res += ask(f[cur][0], x, cur, d, i);
cur = f[cur][0];
}
cur = x;
while (cur != 1)
{
cur = pre[cur];
if (cur == 1) break;
if (vis[f[cur][0]])
{
cur = f[cur][0];
continue;
}
res += ask(f[cur][0], x, cur, d, i);
cur = f[cur][0];
}
for (int i = 1; i <= cntt; i++) vis[g[i]] = 0;
}
printf("%d\n", res);
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构