BJOI2017 树的难题【树的点分治】【线段树(修改/查询/合并)】

本人最近做模拟赛遇到的一道特别好的题目,把此题本人的见解和做法分享出来作为本蒟蒻的又一篇题解,有什么缺点请大家指出,谢谢!(下面附有code)

题目描述

BJOI2017 树的难题【树的点分治】【线段树(修改/查询/合并)】_第1张图片

输入格式

BJOI2017 树的难题【树的点分治】【线段树(修改/查询/合并)】_第2张图片

样例输入

8 4 3 4
-7 9 6 1
1 2 1
1 3 2
1 4 1
2 5 1
5 6 2
3 7 1
3 8 3

输出格式

输出格式

样例输出

11

数据范围

BJOI2017 树的难题【树的点分治】【线段树(修改/查询/合并)】_第3张图片

题解

精简版
这种树上乱七八糟的题显然是要用树的点分治的( ̄▽ ̄),找到当前的根,考虑到颜色段的存在,所以我们排序出边颜色相同的放在一起(为了判重*,在此前提下再把从根出发第一个到的点的编号相同的放在一起),(若有神犇有别的去重方法可以无视这句话) 然后不停查询和维护两颗线段树(一颗存储同色一颗存储异色),每次更换颜色就合并两颗线段树;这样即可统计答案。
详细版
先来想想树的点分治大致怎么解决问题:对于询问树上所有(区间)路径,每次以当前树的重心为根,统计经过或包含这个根的所有路径,统计(维护)答案,然后删去这个根(打个标记等),接着分别处理它的每个子树按照这个步骤继续做下去,得到答案。这种做法实际时间复杂度其实是O(n logn),只是看上去大罢了。那么这道题很明显就可以用树的点分治去做。

接下来再来想想这道题怎么用树的点分治呢…大致的模板还是不变的,问题在于怎么去找到符合要求的路径并且寻找答案。

先来解决第一个子问题:统计经过或包含当前根的长度 l 到 r 之间的路径,这个可以从当前的根跑一边BFS,找到每一条从当前根出发的长度在1-r之间的路径,记录下来这条路的深度,权值和,从根出发的第一条边的颜色,从根出发第一个到的点的编号;这些以后统计答案都会用到。

接下来按照模板,对这些路进行排序,按照每条路从根出发的第一条边的颜色为第一关键字,从根出发第一个到的点的编号为第二关键字从小到大排序(从大到小应该也没有问题)(~ ̄▽ ̄)~

好的,接下来怎么做呢?当然是统计答案了-( ̄▽ ̄)- 可以去维护两颗线段树(下标为经过边的数量),一颗同色,一颗异色,每次从根出发第一个到的点编号不同时就把从当前位置到上一次的位置间的数放进同色线段树中,然后每次遇到不同颜色时,合并两颗线段树即可,然后就可以直接在线段树中按当前剩余需要的边数区间查找答案。

*去重:解释一下,因为我用的方法是把所有经过当前根且不经过被删掉的点的路找出来,所以会匹配时有不合法的情况,如下图:
BJOI2017 树的难题【树的点分治】【线段树(修改/查询/合并)】_第4张图片
这样就会出现走了重复的路,要进行判断

贴一下code:

(打的还是古老的Pascal,并且有些长,重在理解,不喜勿喷)

var
        bz:array[0..1000005]of boolean;
        maxval,maxval2,jlbh,jlhm,c,f,l,map,maps,last,dis,b,cs,la,las,first,jlcs,jl,jlfs:array[0..1000005]of longint;
        head,tail,sum,n,u,v,w,l2,r2,minn,mint,size,ans,m,root,i:longint;

procedure ins(x,y,z:longint);
begin
        inc(sum);
        map[sum]:=y;
        maps[sum]:=z;
        last[sum]:=l[x];
        l[x]:=sum;
end;
function build(x,la:longint):longint; //建树
var
        i,bd,maxson:longint;
begin
        i:=l[x];
        f[x]:=1;
        maxson:=0;
        while i<>0 do
        begin
                if (bz[map[i]])and(map[i]<>la) then
                begin
                        bd:=build(map[i],x);
                        if bd>maxson then
                                maxson:=bd;
                        f[x]:=f[x]+f[map[i]];
                end;
                i:=last[i];
        end;
        if size-maxson-1>maxson then maxson:=size-maxson-1;
        if maxson<minn then
        begin
                minn:=maxson;
                mint:=x;
        end;
        exit(f[x]);
end;
procedure pai(i,j:longint); //排序
var
        l5,r5,mid,mid2,t:longint;
begin
        l5:=i; r5:=j;
        mid:=jlfs[(i+j)div 2];
        mid2:=jlhm[(i+j)div 2];
        while i<j do
        begin
                while (jlfs[i]<mid)or(jlfs[i]=mid)and(jlhm[i]<mid2) do inc(i);
                while (jlfs[j]>mid)or(jlfs[j]=mid)and(jlhm[j]>mid2) do dec(j);
                if i<=j then
                begin
                        t:=jl[i]; jl[i]:=jl[j]; jl[j]:=t;
                        t:=jlfs[i]; jlfs[i]:=jlfs[j]; jlfs[j]:=t;
                        t:=jlcs[i]; jlcs[i]:=jlcs[j]; jlcs[j]:=t;
                        t:=jlhm[i]; jlhm[i]:=jlhm[j]; jlhm[j]:=t;
                        inc(i);
                        dec(j);
                end;
        end;
        if i<r5 then pai(i,r5);
        if l5<j then pai(l5,j);
end;
function max(a1,a2:longint):longint;
begin
        if a1>a2 then
                max:=a1
        else
                max:=a2;
end;
procedure insert(now,nl,nr,sd,qz:longint); //把值放进线段树中
var
        mid:longint;
begin
        if nl=nr then
        begin
                if nl=sd then
                begin
                        if qz>maxval2[now] then
                                maxval2[now]:=qz;
                end;
                exit;
        end;
        if (nl>sd)or(nr<sd) then exit;
        mid:=(nl+nr)div 2;
        insert(now*2,nl,mid,sd,qz);
        insert(now*2+1,mid+1,nr,sd,qz);
        maxval2[now]:=max(maxval2[now*2],maxval2[now*2+1]);
end;
function query(now,nl,nr,mbl,mbr:longint):longint; //在异色树查找答案
var
        mid:longint;
begin
        if (nl>=mbl)and(nr<=mbr) then
        begin
                exit(maxval[now]);
        end;
        if maxval[now]=-1000000000 then exit(-1000000000);
        if (nl>mbr)or(nr<mbl) then exit(-1000000000);
        mid:=(nl+nr)div 2;
        query:=max(query(now*2,nl,mid,mbl,mbr),query(now*2+1,mid+1,nr,mbl,mbr));
end;
function query2(now,nl,nr,mbl,mbr:longint):longint; //在同色树查找答案
var
        mid:longint;
begin
        if (nl>=mbl)and(nr<=mbr) then
        begin
                exit(maxval2[now]);
        end;
        if maxval2[now]=-1000000000 then exit(-1000000000);
        if (nl>mbr)or(nr<mbl) then exit(-1000000000);
        mid:=(nl+nr)div 2;
        query2:=max(query2(now*2,nl,mid,mbl,mbr),query2(now*2+1,mid+1,nr,mbl,mbr));
end;
procedure merge(now,nl,nr:longint); //合并两颗线段树归为异色树
var
        mid:longint;
begin
        if nl=nr then
        begin
                if maxval[now]<maxval2[now] then maxval[now]:=maxval2[now];
                maxval2[now]:=-1000000000;
                exit;
        end;
        if maxval2[now]=-1000000000 then exit;
        mid:=(nl+nr)div 2;
        if maxval[now]<maxval2[now] then maxval[now]:=maxval2[now];
        merge(now*2,nl,mid);
        merge(now*2+1,mid+1,nr);
        maxval2[now]:=-1000000000;
end;
procedure csh(now,nl,nr:longint); //初始化线段树的值
var
        mid:longint;
begin
        if maxval[now]=-1000000000 then exit;
        maxval2[now]:=-1000000000;
        if nl=nr then
        begin
                if nl=0 then
                        maxval[now]:=0
                else
                        maxval[now]:=-1000000000;
                exit;
        end;
        mid:=(nl+nr)div 2;
        csh(now*2,nl,mid);
        csh(now*2+1,mid+1,nr);
        maxval[now]:=max(maxval[now*2],maxval[now*2+1]);
end;

procedure find(x:longint); //寻找以当前点为根的答案
var
        i,l1,r1,s,sz,last2,j,fd:longint;
        bj:boolean;
begin
        head:=0;
        tail:=1;
        b[1]:=x;
        dis[x]:=0; //记录从根走到当前节点的路径权值
        cs[x]:=0;  //记录深度
        la[1]:=0;
        las[1]:=0;
        jlbh[x]:=0; //记录从根出发第一个到的点编号
        first[x]:=0; //记录从根出发第一次经过的边的颜色
        s:=0;
        while head<tail do
        begin
                inc(head);
                i:=l[b[head]];
                while i<>0 do
                begin
                        if (bz[map[i]])and(la[head]<>map[i]) then
                        begin
                                inc(tail);
                                b[tail]:=map[i];
                                la[tail]:=b[head];
                                if las[head]<>maps[i] then
                                        dis[map[i]]:=dis[b[head]]+c[maps[i]]
                                else
                                        dis[map[i]]:=dis[b[head]];
                                las[tail]:=maps[i];
                                first[map[i]]:=first[b[head]];
                                jlbh[map[i]]:=jlbh[b[head]];
                                if jlbh[map[i]]=0 then
                                        jlbh[map[i]]:=map[i];
                                if first[map[i]]=0 then
                                        first[map[i]]:=maps[i];
                                cs[map[i]]:=cs[b[head]]+1;
                                inc(s);
                                jl[s]:=dis[map[i]]; //记录从根走到当前节点的路径权值
                                jlfs[s]:=first[map[i]]; //记录从根出发第一次经过的边的颜色
                                jlcs[s]:=cs[map[i]]; //记录深度
                                jlhm[s]:=jlbh[map[i]]; //记录从根出发第一个到的点编号
                        end;
                        i:=last[i];
                end;
        end;
        if s=0 then exit;
        pai(1,s); 
        last2:=0;
        csh(1,0,r2); //给树赋上初值
        bj:=false;
        for i:=1 to s do
        begin
                if r2-jlcs[i]>=0 then
                begin
                        fd:=query(1,0,r2,max(0,l2-jlcs[i]),max(0,r2-jlcs[i])); //查找答案
                        if (jlfs[i]=jlfs[i-1])and(bj) then
                                fd:=max(fd,query2(1,0,r2,max(0,l2-jlcs[i]),max(0,r2-jlcs[i])))-c[jlfs[i]];
                        if (fd>-1000000000)and(fd+jl[i]>ans) then
                                ans:=fd+jl[i];
                end;
                if jlhm[i]<>jlhm[i+1] then
                begin
                        bj:=true;
                        for j:=last2+1 to i do
                                if jlcs[j]<=r2 then
                                        insert(1,0,r2,jlcs[j],jl[j]);//修改同色树
                        last2:=i;
                end;
                if jlfs[i]<>jlfs[i+1] then
                begin
                        merge(1,0,r2); //合并两棵树归为异色树
                        bj:=false;
                end;
        end;
end;

procedure dg(x:longint); //点分治操作中心
var
        i,root:longint;
begin
        find(x);
        bz[x]:=false;
        i:=l[x];
        while i<>0 do
        begin
                if bz[map[i]] then
                begin
                        size:=f[map[i]];
                        minn:=maxlongint; mint:=0;
                        root:=build(map[i],0);
                        dg(mint);
                end;
                i:=last[i];
        end;
end;

begin //主程序
        assign(input,'journey.in');
        reset(input);
        assign(output,'journey.out');
        rewrite(output);
        readln(n,m,l2,r2);
        for i:=1 to m do
        begin
                read(c[i]);
        end;
        for i:=1 to n do bz[i]:=true;
        for i:=1 to n-1 do
        begin
                readln(u,v,w);
                ins(u,v,w);
                ins(v,u,w);
        end;
        ans:=-maxlongint;
        randomize;
        minn:=maxlongint;  mint:=0;
        size:=n;
        root:=build(random(n)+1,0);
        root:=build(mint,0);
        dg(mint);
        writeln(ans);
end.


谢谢大家观看!

你可能感兴趣的:(算法,数据结构)