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.
posted @ 2018-10-25 13:43  _ARFA  阅读(121)  评论(0编辑  收藏  举报