题解 P4216 [SCOI2015]情报传递
题意简述
给定一棵 \(n\) 个节点的树, \(m\) 次操作:
2 x
让 \(x\) 的权值从当前时刻开始,每一个时刻 \(+1\) (当前时刻的权值仍为 \(0\) )。1 x y c
询问 \(x\) 到 \(y\) 路径上有多少个点,并且输出有多少个点的权值 \(>c\) 。
每操作一次,时刻就会 \(+1\) ,最开始时(第 \(1\) 次操作时)时间为 \(1\) 。
\(1 \leq n,m \leq 2\times 10^5\) 。
Solution
输出路径上有多少个点很简单,就是 \(dep_u+dep_v-2 \times dep_{lca(u,v)}+1\) 。下面重点讨论有多少个点的权值 \(>c\) 。
下面的「点亮」操作指的就是操作 \(2\) 。
设第 \(k\) 个点被点亮的时间是 \(st_k\) ,那么 \(x\) 到 \(y\) 的路径上权值 \(>c\) 就等价于:
\[i-st_k>c
\]
变形可得:
\[st_k\leq i-c-1
\]
那么就可以把问题转化为:给定一棵 \(n\) 个节点的树,每个节点的权值初始为 \(\infty\) , \(m\) 次操作:
- 修改第 \(i\) 个点的权值
- 查询一条路径上 \(\leq\) 一个值的权值的个数。
这可以用树剖 \(+\) 树套树解决(当然也可以带修改的主席树),但这明显都不好实现,想想有没有更好地方法?
发现本题可以离线,于是我们可以按照每个操作 查询\(/\)修改的时刻排序。(第 \(i\) 个操作如果是修改,时刻就是 \(i\) ,如果是查询,时刻是 \(i-c-1\) ),对于时刻相同的操作,先进行修改,再进行查询。
然后查询就变成了问树上一条路径上有几个点被点亮过了。
这样就可以把点亮操作变成是把这个节点的权值设为 \(1\) ,把查询有几个点被点亮过了变成求树上路径和。
这就可以用树剖 \(+\) 树状数组解决,很好写,而且跑得飞快第一次拿第一!:
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
char buf[1 << 25] ,*p1 = buf ,*p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf ,1 ,1 << 21, stdin) ,p1 == p2) ? EOF : *p1++)
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 2e5 + 5 ,M = 2e5 + 5 ,INF = 0x3f3f3f3f;
struct BIT {
int t[N] ,n;
BIT (int n = 0) : n(n) {memset(t ,0 ,sizeof(t));}
inline int lowbit(int x) {return x & (-x);}
inline void add(int x ,int k) {
for (int i = x; i <= n; i += lowbit(i)) t[i] += k;
}
inline int sum(int x) {
int ans = 0;
for (int i = x; i ; i -= lowbit(i)) ans += t[i];
return ans;
}
}t;
struct Edge {
int to ,next;
Edge (int to = 0 ,int next = 0) : to(to) ,next(next) {}
}G[M]; int head[N] ,cnt;
inline void add(int u ,int v) {
G[++cnt] = Edge(v ,head[u]); head[u] = cnt;
}
int fa[N] ,dep[N] ,siz[N] ,son[N] ,top[N] ,id[N] ,dfn;
inline void dfs1(int now) {
dep[now] = dep[fa[now]] + 1;
siz[now] = 1;
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now]) continue;
fa[v] = now;
dfs1(v);
siz[now] += siz[v];
if (siz[v] > siz[son[now]]) son[now] = v;
}
}
inline void dfs2(int now ,int t) {
top[now] = t;
id[now] = ++dfn;
if (!son[now]) return ;
dfs2(son[now] ,t);
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now] || v == son[now]) continue;
dfs2(v ,v);
}
}
inline void modify(int x) {t.add(id[x] ,1);}
inline int query(int x ,int y) {
int ans = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x ,y);
ans += t.sum(id[x]) - t.sum(id[top[x]] - 1);
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x ,y);
ans += t.sum(id[x]) - t.sum(id[y] - 1);
return ans;
}
inline int LCA(int x ,int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x ,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
struct Queries {
int opt ,id ,t ,x ,y;
Queries(int opt = 0 ,int id = 0 ,int t = 0 ,int x = 0 ,int y = 0) :
opt(opt) ,id(id) ,t(t) ,x(x) ,y(y) {}
friend bool operator < (const Queries a ,const Queries b) {
if (a.t != b.t) return a.t < b.t;
return a.opt < b.opt;
}
}q[N];
int n ,m ,rt ,ans[N] ,res[N];
signed main() {
t.n = n = read();
for (int i = 1; i <= n; i++) {
int fa = read();
if (fa == 0) rt = i;
else add(fa ,i);
}
dfs1(rt); dfs2(rt ,rt);
m = read();
for (int i = 1; i <= m; i++) {
int opt = read() ,x = read();
if (opt == 2) q[i] = Queries(1 ,i ,i ,x ,0);
else {
int y = read() ,z = read();
q[i] = Queries(2 ,i ,i - z - 1 ,x ,y);
}
}
memset(ans ,-1 ,sizeof(ans));
sort(q + 1 ,q + m + 1);
for (int i = 1; i <= m; i++) {
int opt = q[i].opt ,x = q[i].x ,y = q[i].y ,id = q[i].id;
if (opt == 1) modify(x);
else {
ans[id] = query(x ,y);
int lca = LCA(x ,y);
res[id] = dep[q[i].x] + dep[q[i].y] - dep[lca] * 2 + 1;
}
}
for (int i = 1; i <= m; i++)
if (ans[i] != -1) printf("%d %d\n" ,res[i] ,ans[i]);
return 0;
}
从这题中得到的启示
- 对于有关时间的问题,可以考虑能不能离线,然后按顺序处理。