P3258 [JLOI2014]松鼠的新家
不知道那位 \(Pascal\) 的仁兄是怎么想的,不用修改为什么要打树剖? 为什么要打线段树?
还是先简化一下题面:
- 给你一棵树
- 给你 \(N\) 个点,要你对每一个 \(node_i\) 到 \(node_{i+1}\) 的树上路径的点权全部 \(+1\)。
- 最后输出每一个点的点权
带着打树剖的情绪,发现时间复杂度为 \(O(N \log^2 N)\)。显然不用修改,并不需要树剖。现在我们需要一个可以直接修改,且可以最后查询的数据结构 (或者某种方法)。很容易想到可以差分。
先说差分数组,如果我们给出 \(M\) 个修改,在一个区间加上一个数字。最后问你这个序列是多少。先举一个例子:
原数组 : 3 4 9 2 3 (num)
差分数组 : 3 1 5 -7 1 (sum)
很显然差分数组 \(sum_i=num_i-num_{i-1}\)。
而我们给 \(2,4\) 区间加上一个 \(3\),可以这样子。
差分数组 : 3 4 5 -7 -2 (sum)
我们发现 \(sum_l+3\ ,\ sum_{r+1}-3\)。
求一下前缀和。
前缀和 : 3 7 12 5 3
发现了什么?
树上差分更简单。假如我们要使 \(l->r\) 这一条路径全部 \(+1\),我们可以在它们的 \(sum_{lca}\) 上面 \(-1\),\(sum_{lca_{father}}\) 也是 \(-1\)。然后 \(sum_l+1,sum_r+1\)。有人会问,为什么要在 \(sum_{lca_{father}}-1\) 呢? 因为 \(lca\) 也是要 \(+1\) 的,如果求前缀和那么 \(lca\) 这里就 \(+2\) 了。然而 \(lca\) 已经 \(-1\),\(lca_{father}\) 是不用 \(+1\) 的。所以直接 \(sum_{lca_{father}}-1\) 就巧妙的解决了问题。
然后你会发现你输出了以下东西
1 3 2 3 2
而答案是
1 2 1 2 1
因为我们把除 \(node_1\) (注意是 \(node_1\) 不是 \(1\)) 以外的都求了两次 (起点终点),所以我们要把它们减一。
因为要求 \(lca\),所以时间复杂度为 \(O(N \log N)\)。其中 \(lca\) 用树剖求,更快。 (特别对于一条链的情况)
// 总共 400ms,第 11 页 (已经很不错啦)
// luogu-judger-enable-o2
var
cnt,size,dep,top,father,son,node,sum:array[-1..510000] of longint;
next,reach:array[-1..1050000] of longint;
j,i,n,m,l,r,tot,root,lca:longint;
procedure add(l,r:longint);
begin
inc(tot);
reach[tot]:=r;
next[tot]:=cnt[l];
cnt[l]:=tot;
end;
procedure Dfs_1(x:longint);
var i:longint;
begin
size[x]:=1; i:=cnt[x]; size[0]:=-maxlongint div 843;
while i<>-1 do
begin
if dep[reach[i]]=0 then
begin
dep[reach[i]]:=dep[x]+1;
father[reach[i]]:=x;
Dfs_1(reach[i]); inc(size[x],size[reach[i]]);
if size[reach[i]]>size[son[x]] then son[x]:=reach[i];
end;
i:=next[i];
end;
end;
procedure Dfs_2(x,centre:longint);
var i:longint;
begin
top[x]:=centre;
if son[x]=0 then exit; Dfs_2(son[x],centre);
i:=cnt[x];
while i<>-1 do
begin
if (reach[i]<>father[x])and(reach[i]<>son[x]) then Dfs_2(reach[i],reach[i]);
i:=next[i];
end;
end;
procedure Dfs_3(x:longint);
var i:longint;
begin
i:=cnt[x];
while i<>-1 do
begin
if (reach[i]<>father[x]) then
begin
Dfs_3(reach[i]);
inc(sum[x],sum[reach[i]]);
end;
i:=next[i];
end;
end;
function Refer(x,y:longint):int64;
begin
while top[x]<>top[y] do
begin
if dep[top[x]]<dep[top[y]] then y:=father[top[y]] else x:=father[top[x]];
end;
if dep[x]<dep[y] then exit(x) else exit(y);
end;
begin
filldword(cnt,sizeof(cnt) div 4,maxlongint*2+1);
read(n); root:=1;
for i:=1 to n do read(node[i]);
for i:=1 to n-1 do begin read(l,r); add(l,r); add(r,l); end;
dep[root]:=1; father[root]:=0; Dfs_1(root); Dfs_2(root,root);
for i:=1 to n-1 do
begin
lca:=Refer(node[i+1],node[i]);
inc(sum[node[i]]);
inc(sum[node[i+1]]);
dec(sum[father[lca]]);
dec(sum[lca]);
end;
Dfs_3(root);
for i:=2 to n do dec(sum[node[i]]);
for i:=1 to n do writeln(sum[i]);
end.