题目链接:Noip2016天天爱跑步 .
—————————————-
概述
题目大意如下:
给定一棵
n
个点的树,每一个点有一个点权
—————————————-
分析
我当时考试的时候,看到这题第一眼感觉就是十分不可做,所以打了60分暴力草草了事。即使到现在,我仍感觉这道题绝对不是Noip的难度,毕竟涉及的算法思想不是很容易就能想到,应该有Noi的级别。
言归正传,我们观察一下这题。
根据套路,我们设1号节点为根,预处理出每一条路径 u,v 的 lca ,这里我用的是树链剖分。(因不会tarjon而瑟瑟发抖)
可以发现,假如我们的思路总是跟着“路径上的每一个点能获得的贡献”去想,复杂度怎么都是 O(nm) ,不会有优化的空间。所以我们可以避开考虑每一条路径能为路径上的点所提供的贡献,而是考虑每一个点如何能从路径上获得贡献。
我们对每个点
x
这么考虑:对
u 比
x 深度大,即 u 在x 下方。此时有:
dep[x]+w[x]=dep[u].我们能够发现等式左边的值只跟 x 有关,确定了
x 就确定了整个等式。那么对 x “可能”有贡献的起点
u 即是在 x 的子树中,且深度为dep[x]+w[x] 的起点。注意一下,上文我说“可能”有贡献,是因为以这个 u 为起点的路径可能不会经过
x ,也就是说: u 与v 的 lca 可能在 x 的下方,即dep[lca]>dep[x] 。这个情况我们在后面再考虑如何消除。我们建一个桶 cnt[i] 表示处理到当前时深度为 i 的起点的个数。那如何获得在
x 的子树中深度为 dep[x]+w[x] 的起点的个数呢?记 dep[x]+w[x]=t[x] ,我们可以发现,假如我们把初次搜索到 x 时的
cnt[t[x]] 记录下来,等搜索完 x 的子树后回溯到x 时,此时新的 cnt[[x]] 与初次的 cnt[t[x]] 的差值即是 x 子树中深度为t[x] 的起点个数。好了,统计的问题我们解决了,那么上面所提到的不应该有的贡献如何消除呢?
我们思考一下,假如 u 在
x 的子树内, lca 若在 x 的子树外则能保证这条路径一定经过x ,能对 x 产生贡献,相反地,lca 若在 x 的子树内则没有贡献。所以,当我们处理完
x 这个点时,我们可以把所有 lca 为 x 的路径的贡献删除,这样回溯到上面的时候就可以把本不应该有的贡献消除了。第一种情况讨论完毕。
u 的深度比 x 小,u 在 x 上方。这个时候起点
u 不在 x 的子树里,没有办法统计,所以我们可以换过来统计终点v 的贡献。此时有:
dep[x]−w[x]=dep[v]−dis(u,v).我们观察到等式左边仍是只与 x 有关的量,确定了
x 就能确定哪些终点对它有贡献。类比第一种情况,我们另开一个桶 cnt2[i] 统计处理到当前点时, dep[v]−dis(u,v) 为 i 的终点的个数。统计方法和第一种情况一样,记
dep[x]−w[x] 为 t[x] ,记录初次搜索到 x 时的cnt2[t[x]] ,处理完 x 的子树回溯到x 时,用新的 cnt2[t[x]] 减去初次的值就是在 x 的子树内的终点个数。之后再把lca 为 x 的终点的贡献删除掉就行了。有一个小细节,
dep[v]−dis(u,v) 和 dep[x]−w[x] 也许会是负数,我们只需要将数组整体向右平移就完事了。第二种情况讨论完毕。
值得注意的是,对于一条路径,假如一个观察点 x 它同时满足上面两种情况,那么这条路径的贡献就会被算2次,所以我们要减掉这多算的贡献。
观察一下,假如观察点
这两个式子相加,我们得到:
也就是说,只有当
x
是这条路径的
再把两个式子相减,进而得到:
这个式子的意思是,在
x
是路径的
所以,我们只需要枚举每一条路径的
综上,这道题的解法就讲完了,时间复杂度 O(n log n).
—————————————-
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
#define For(i, j, k) for(int i = j; i <= (int)k; ++ i)
#define Forr(i, j, k) for(int i = j; i >= (int)k; -- i)
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 300000 + 5;
int n, m, tot, maxdep;
int w[maxn], s[maxn], cnt1[maxn], cnt2[maxn<<2], Ans[maxn];
int size[maxn], dep[maxn], son[maxn], fa[maxn], top[maxn];
int Cnt, Begin[maxn], Next[maxn<<1], To[maxn<<1];
vector<int> q1[maxn], q2[maxn], q3[maxn];
struct player{
int u, v, lca, dis;
}a[maxn];
inline int Read(){
int x = 0; char c = getchar();
for(; !isdigit(c); c = getchar());
for(; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
return x;
}
inline void Swap(int &x, int &y) {int temp = x; x = y; y = temp;}
inline void Add(int x, int y){
To[++Cnt] = y;
Next[Cnt] = Begin[x];
Begin[x] = Cnt;
}
void dfs1(int x){
size[x] = 1;
for(int i = Begin[x],v; i; i = Next[i]){
v = To[i];
if(v != fa[x]){
fa[v] = x; dep[v] = dep[x] + 1;
dfs1(v);
size[x] += size[v];
if(size[v] > size[son[x]]) son[x] = v;
}
}
}
void dfs2(int x, int tp){
top[x] = tp;
if(son[x]) dfs2(son[x], tp);
for(int i = Begin[x],v; i; i = Next[i]){
v = To[i];
if(v != fa[x] && v != son[x])
dfs2(v, v);
}
}
inline int Lca(int x, int y){
int tx, ty;
while((tx = top[x]) != (ty = top[y])){
if(dep[tx] < dep[ty]) Swap(x, y), Swap(tx, ty);
x = fa[tx];
}
if(dep[x] > dep[y]) Swap(x, y);
return x;
}
void dfs(int x){
int now = w[x] + dep[x], cun; if(now <= maxdep) cun = cnt1[now];
for(int i = Begin[x],v; i; i = Next[i]){
v = To[i];
if(v != fa[x]) dfs(v);
}
cnt1[dep[x]] += s[x]; if(now <= maxdep) Ans[x] = cnt1[now] - cun;
For(i, 0, q1[x].size()-1) -- cnt1[dep[q1[x][i]]];
}
void DFS(int x){
int now = dep[x] - w[x] + maxn, cun = cnt2[now];
for(int i = Begin[x],v; i; i = Next[i]){
v = To[i];
if(v != fa[x]) DFS(v);
}
For(i, 0, q2[x].size()-1) ++ cnt2[q2[x][i]+maxn];
Ans[x] += cnt2[now] - cun;
For(i, 0, q3[x].size()-1) -- cnt2[q3[x][i]+maxn];
}
int main(){
int x, y;
n = Read(), m = Read();
For(i, 1, n - 1){
x = Read(), y = Read();
Add(x, y), Add(y, x);
}
For(i, 1, n) w[i] = Read();
For(i, 1, m) a[i].u = Read(), a[i].v = Read();
dep[1] = 1, dfs1(1); dfs2(1, 1);
For(i, 1, n) maxdep = max(maxdep, dep[i]);
For(i, 1, m){
a[i].lca = Lca(a[i].u, a[i].v); ++ s[a[i].u];
a[i].dis = dep[a[i].u] + dep[a[i].v] - (dep[a[i].lca] << 1);
q1[a[i].lca].push_back(a[i].u);
}
dfs(1);
For(i, 1, m){
q2[a[i].v].push_back(dep[a[i].v] - a[i].dis);
q3[a[i].lca].push_back(dep[a[i].v] - a[i].dis);
}
DFS(1);
For(i, 1, m) if(dep[a[i].u] - dep[a[i].lca] == w[a[i].lca]) -- Ans[a[i].lca];
For(i, 1, n) printf("%d ", Ans[i]);
return 0;
}
—————————————-
小结
经过2天的时间, Noip2016 最难的一道题终于被攻克下来了。题目用到的算法虽然很基础,但是分析思考的过程十分的耐人寻味,或许我就是欠缺这种思维的练习。总的来说,这是道难得的好题!
—————————————-
——wrote by miraclejzd.