gym102452 (点分治)

gym102452 (点分治)

题意:

点有点权,问树上路径数使得路径点权能构成一个多边形

思路:

香港区域赛的一题,当时vp的时候还不会点分,现在会了根本就是煞笔题嘛…

显然是问 2 ∗ m x < s u m 2*mx<sum 2mx<sum的路径数,由于枚举子树的时候要分类讨论,所以我们用容斥方法计数,存下从分治重心出发的路径。

按照 m x mx mx排序后,固定最大值枚举,假设枚举的两条链分别为 x 1 x1 x1, x 2 x2 x2,由于根算重了,则转化为 2 ∗ m x < x 1 + x 2 − r t 2*mx<x1+x2-rt 2mx<x1+x2rt,用树状数组单点修改,查询后缀和。

这种点权除了忽略根节点计算,我们还可以包括根节点然后再去除它的影响。注意这种做法以重心为根的端点也是统计在内的

#include<bits/stdc++.h>
#define fi first 
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=2e5+5;
P tmp[maxn];
int head[maxn],ver[maxn<<1],next1[maxn<<1],tot,S,sz[maxn],mxson[maxn],rt,a[maxn],t,cnttmp,n;
bool v[maxn];
ll d[maxn],ans;
void add(int x,int y){
    ver[++tot]=y,next1[tot]=head[x],head[x]=tot;
}
struct BIT{
    #define lowb(x) (x&(-x))
    ll c[maxn],N;
    void init(int x){
        N=x;
    }
    void add(int x,int val){
        while(x<=N){
            c[x]+=val;
            x+=lowb(x);
        }
    }
    ll ask(int x){
        ll ans=0;
        while(x){
            ans+=c[x];
            x-=lowb(x);
        }
        return ans;
    }
}bit;
void getRoot(int x,int f){
    sz[x]=1;mxson[x]=0;
    for(int i=head[x];i;i=next1[i]){
        int y=ver[i];
        if(y==f||v[y])continue;
        getRoot(y,x);
        sz[x]+=sz[y];
        mxson[x]=max(mxson[x],sz[y]);
    }
    mxson[x]=max(mxson[x],S-mxson[x]);
    if(mxson[x]<mxson[rt])rt=x;
}
void getQue(int x,int f,int mx,ll val){
    tmp[++cnttmp]={mx,val};
    for(int i=head[x];i;i=next1[i]){
        int y=ver[i];
        if(y==f||v[y])continue;
        getQue(y,x,max(mx,a[y]),val+a[y]);
    }
}
ll cal(int x,int mx,ll val,int rtval){
    cnttmp=0;
    ll sum=0;
    getQue(x,0,mx,val);
    for(int i=1;i<=cnttmp;++i)d[i]=tmp[i].se;
    sort(d+1,d+1+cnttmp);
    int num=unique(d+1,d+1+cnttmp)-(d+1);
    bit.init(num);
    sort(tmp+1,tmp+1+cnttmp);
    for(int i=1;i<=cnttmp;++i){
        ll dd=-tmp[i].se+2*tmp[i].fi;
        int pos=upper_bound(d+1,d+1+num,dd+rtval)-d;//查的时候得把根的贡献算进去
        if(pos<=num)
            sum+=bit.ask(num)-bit.ask(pos-1);
        int pos2=lower_bound(d+1,d+1+num,tmp[i].se)-d;
        bit.add(pos2,1);
    }
    for(int i=1;i<=cnttmp;++i){
        bit.add(lower_bound(d+1,d+1+num,tmp[i].se)-d,-1);
    }
    return sum;
}
void init(int x){
    mxson[rt=0]=maxn;
    S=sz[x];
    getRoot(x,0);
}
void dfz(int x){
    v[x]=1;
    ans+=cal(x,a[x],a[x],a[x]);
    for(int i=head[x];i;i=next1[i]){
        int y=ver[i];
        if(v[y])continue;
        ans-=cal(y,max(a[x],a[y]),a[x]+a[y],a[x]);
        init(y);
        dfz(rt);
    }
    v[x]=0;
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        tot=ans=0;
        for(int i=1;i<=n;++i)head[i]=0;
        for(int i=1;i<=n;++i)scanf("%d",&a[i]);
        int u,v;
        for(int i=1;i<n;++i){
            scanf("%d%d",&u,&v);
            add(u,v);add(v,u);
        }
        mxson[rt=0]=maxn;
        S=n;       
        getRoot(1,0);
        dfz(rt);
        cout<<ans<<"\n";
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值