本人最近做模拟赛遇到的一道特别好的题目,把此题本人的见解和做法分享出来作为本蒟蒻的又一篇题解,有什么缺点请大家指出,谢谢!(下面附有code)
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
精简版:
这种树上乱七八糟的题显然是要用树的点分治的( ̄▽ ̄),找到当前的根,考虑到颜色段的存在,所以我们排序出边颜色相同的放在一起(为了判重*,在此前提下再把从根出发第一个到的点的编号相同的放在一起),(若有神犇有别的去重方法可以无视这句话) 然后不停查询和维护两颗线段树(一颗存储同色一颗存储异色),每次更换颜色就合并两颗线段树;这样即可统计答案。
详细版:
先来想想树的点分治大致怎么解决问题:对于询问树上所有(区间)路径,每次以当前树的重心为根,统计经过或包含这个根的所有路径,统计(维护)答案,然后删去这个根(打个标记等),接着分别处理它的每个子树按照这个步骤继续做下去,得到答案。这种做法实际时间复杂度其实是O(n logn),只是看上去大罢了。那么这道题很明显就可以用树的点分治去做。
接下来再来想想这道题怎么用树的点分治呢…大致的模板还是不变的,问题在于怎么去找到符合要求的路径并且寻找答案。
先来解决第一个子问题:统计经过或包含当前根的长度 l 到 r 之间的路径,这个可以从当前的根跑一边BFS,找到每一条从当前根出发的长度在1-r之间的路径,记录下来这条路的深度,权值和,从根出发的第一条边的颜色,从根出发第一个到的点的编号;这些以后统计答案都会用到。
接下来按照模板,对这些路进行排序,按照每条路从根出发的第一条边的颜色为第一关键字,从根出发第一个到的点的编号为第二关键字从小到大排序(从大到小应该也没有问题)(~ ̄▽ ̄)~
好的,接下来怎么做呢?当然是统计答案了-( ̄▽ ̄)- 可以去维护两颗线段树(下标为经过边的数量),一颗同色,一颗异色,每次从根出发第一个到的点编号不同时就把从当前位置到上一次的位置间的数放进同色线段树中,然后每次遇到不同颜色时,合并两颗线段树即可,然后就可以直接在线段树中按当前剩余需要的边数区间查找答案。
*去重:解释一下,因为我用的方法是把所有经过当前根且不经过被删掉的点的路找出来,所以会匹配时有不合法的情况,如下图:
这样就会出现走了重复的路,要进行判断
(打的还是古老的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.
谢谢大家观看!