JZOJ 1166 树中点对距离

树中点对距离

题目大意

给出一棵带边权的树,问有多少对点的距离<= Len

数据范围

2 <=n<= 10000 len < 231

题解

这是一道十分经典的点分治题目。
点分治,顾名思义,就是按照点来分治。

假设当前要计算某棵子树里面满足题目条件的点对数,我们可以先找出这棵子树的重心。
易知,此棵子树的点对数=经过重心的合法点对数+不经过重心的合法点对数。

我们先求经过重心的合法点对数,计算出此子树里面所有的点到重心的距离,计入数组 dis ,然后排序。计算这颗子树的答案时,维护两个指针 i ,j i j的联系如下,满足 disi + disj <= len j 尽量大。
可以证明,i向右移动时, j 一定不会向右移。
我们就可以借此求出答案,循环终止条件为i> j
大概代码如下:

i:=0;
j:=m; // dis数组的长度为m
while i<j do
begin
    inc(i);
    while dis[i]+dis[j]>len do dec(j);
    if i>=j then break;
    ans:=ans+(j-i);
end;

但我们会发现,这样做的话答案会wrong,这是因为我们可能把同一颗子树里的点对算重复了,
于是我们求答案时需要多维护一个数组把同一棵子树的非法点对减掉,这样就可以了。

找完经过重心的点对答案后,就剩不经过重心的合法点对数,然后我们按照重心把此棵子树分成多棵子树,分治下去,对分出来的子树执行上述操作统计答案即可。

Code(Pascal)

var
    bz:array[0..12000] of boolean;
    en,ph,size,qz:array[0..12000] of longint;
    px:array[0..20000,1..2] of int64;
    bj:array[0..20000,1..3] of longint;
    len,n,m,j,k,l,i,o,p,cqy,xd,ans:longint;
procedure qsortpx(l,r:longint);
    var
        i,j,m:longint;
    begin
        i:=l;
        j:=r;
        m:=px[(l+r) div 2,1];
        repeat
            while px[i,1]<m do inc(i);
            while px[j,1]>m do dec(j);
            if i<=j then
            begin
                px[0]:=px[i];
                px[i]:=px[j];
                px[j]:=px[0];
                inc(i);
                dec(j);
            end;
        until i>j;
        if l<j then qsortpx(l,j);
        if i<r then qsortpx(i,r);
    end;
procedure qsort(l,r:longint);
    var
        i,j,m:longint;
    begin
        i:=l;
        j:=r;
        m:=bj[(l+r) div 2,1];
        repeat
            while bj[i,1]<m do inc(i);
            while bj[j,1]>m do dec(j);
            if i<=j then
            begin
                bj[0]:=bj[i];
                bj[i]:=bj[j];
                bj[j]:=bj[0];
                inc(i);
                dec(j);
            end;
        until i>j;
        if l<j then qsort(l,j);
        if i<r then qsort(i,r);
    end;
function bl(o,dx:longint):longint;
    var
        i:longint;
    begin
        size[o]:=1;
        for i:=en[o-1]+1 to en[o] do
        if bz[bj[i,2]] and (ph[bj[i,2]]<>xd) then
        begin
            ph[bj[i,2]]:=xd;
            bl:=bl(bj[i,2],dx);
            if bl>0 then exit;
            size[o]:=size[o]+size[bj[i,2]];
        end;
        if size[o]>=dx-size[o] then exit(o);
        exit(0);
    end;
procedure cqyilo(o,cq,qy:longint);
    var
         i,j,k:longint;
    begin
        inc(cqy);
        px[cqy,1]:=cq;
        px[cqy,2]:=qy;
        size[o]:=1;
        for i:=en[o-1]+1 to en[o] do
        if bz[bj[i,2]] and (ph[bj[i,2]]<>xd) then
        begin
            ph[bj[i,2]]:=xd;
            cqyilo(bj[i,2],cq+bj[i,3],qy);
            size[o]:=size[o]+size[bj[i,2]];
        end;
    end;
procedure dg(o,dd:longint);
    var
        i,j,l,k,p:longint;
    begin
        inc(xd);
        k:=bl(o,dd);
        bz[k]:=false;
        cqy:=0;
        inc(xd);
        for i:=en[k-1]+1 to en[k] do
        if bz[bj[i,2]] then
        begin
            ph[bj[i,2]]:=xd;
            cqyilo(bj[i,2],bj[i,3],i-en[k-1]);
            qz[i-en[k-1]]:=size[bj[i,2]];
        end;
        qsortpx(1,cqy);
        for i:=1 to cqy do
        if px[i,1]<=len then inc(ans)
        else break;
        i:=0;
        j:=cqy;
        px[0,1]:=-maxlongint;
        while i<j do
        begin
            inc(i);
            dec(qz[px[i,2]]);
            while px[i,1]+px[j,1]>len do
            begin
                dec(qz[px[j,2]]);
                dec(j);
            end;
            if i>=j then break;
            ans:=ans+(j-i-qz[px[i,2]]);
        end;
        for i:=en[k-1]+1 to en[k] do
        if (size[bj[i,2]]>1) and bz[bj[i,2]] then
        dg(bj[i,2],size[bj[i,2]]);
    end;
begin
    readln(n,len);
    for i:=1 to n-1 do
    begin
        readln(bj[i*2-1,1],bj[i*2-1,2],bj[i*2-1,3]);
        bj[i*2,1]:=bj[i*2-1,2];
        bj[i*2,2]:=bj[i*2-1,1];
        bj[i*2,3]:=bj[i*2-1,3];
        inc(en[bj[i*2,1]]);
        inc(en[bj[i*2,2]]);
    end;
    for i:=1 to n do
    en[i]:=en[i-1]+en[i];
    qsort(1,2*n-2);
    for i:=1 to n do
    bz[i]:=true;
    dg(1,n);
    writeln(ans);
end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值