LOJ#6463 AK YOI 树分治+线段树合并

传送门

既然是树上路径统计问题,不难想到要使用树分治,这里以点分治为例

由点分治的性质,每层只需要考虑经过重心的路径

因为需要维护路径长度在一定范围内的最大权值和,所以要用一个数据结构维护一下到根节点距离在一定范围内的最大权值和

显然线段树是一个不错的选择,对每个子树建立一个线段树,根节点的答案用每个子树的线段树都更新一遍即可

考虑更新子树中的点的答案,这时需要使用除这棵子树外的所有子树的线段树一起更新

我们可以使用线段树合并来维护,给子树任意确定一个顺序,然后通过维护每个子树的前缀和后缀线段树的并即可快速得到除去某棵子树后的线段树

显然复杂度是有保证的,因为线段树合并的复杂度无论如何都不会高于暴力插入三遍

总复杂度\(O(n\log^2 n)\),常数应该不小,不过跑的挺快233333

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005,maxm=maxn*100;
const long long INF=0x5f5f5f5f5f5f5f5fll;
void solve(int,int);
int getcenter(int,int);
int getdis(int);
void getans(int,int);
void modify(int,int,int&);
int merge(int,int);
long long query(int,int,int);
long long mx[maxm];
int lc[maxm],rc[maxm],cnt=0,root[maxn],prefix[maxn],suffix[maxn];
vector<int>G[maxn];
bool vis[maxn];
long long w[maxn],ans[maxn],ant[maxn],tmp;
int p[maxn],size[maxn],son[maxn],q[maxn],d[maxn],pr[maxn],nx[maxn];
int n,m,L,R,val[maxn],s,t;
int main(){
    mx[0]=-INF;
    scanf("%d%d%d",&n,&L,&R);
    for(int i=1;i<=n;i++){
        scanf("%d",&val[i]);
        ans[i]=-3472328296227680305ll;
    }
    for(int i=1,x,y;i<n;i++){
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    solve(1,n);
    for(int i=1;i<=n;i++){
        if(i>1)printf(" ");
        printf("%lld",ans[i]);
    }
    printf("\n");
    return 0;
}
void solve(int x,int sz){
    x=getcenter(x,sz);
    m=sz;
    vis[x]=true;
    w[x]=val[x];
    d[x]=0;
    if(sz==1)return;
    s=0;
    tmp=w[x];
    modify(0,m,root[x]);
    for(int i=0;i<(int)G[x].size();i++)
        if(!vis[G[x][i]]){
            p[G[x][i]]=x;
            getdis(G[x][i]);
        }
    s=L;t=R;
    for(int i=0,last=0;i<(int)G[x].size();i++)
        if(!vis[G[x][i]]){
            if(s<=m)ans[x]=max(ans[x],query(0,m,root[G[x][i]]));
            prefix[G[x][i]]=merge(prefix[last],root[G[x][i]]);
            pr[G[x][i]]=last;
            last=G[x][i];
        }
    ant[x]=-INF;
    for(int i=(int)G[x].size()-1,last=0;~i;i--)
        if(!vis[G[x][i]]){
            suffix[G[x][i]]=merge(suffix[last],root[G[x][i]]);
            nx[G[x][i]]=last;
            last=G[x][i];
        }
    for(int i=0;i<(int)G[x].size();i++)
        if(!vis[G[x][i]])getans(G[x][i],val[x]);
    ans[x]=max(ans[x],ant[x]);
    root[x]=0;
    for(int i=0;i<(int)G[x].size();i++)
        if(!vis[G[x][i]]){
            pr[G[x][i]]=nx[G[x][i]]=0;
            root[G[x][i]]=prefix[G[x][i]]=suffix[G[x][i]]=0;
        }
    cnt=0;
    for(int i=0;i<(int)G[x].size();i++)
        if(!vis[G[x][i]])solve(G[x][i],size[G[x][i]]);
}
int getcenter(int x,int s){
    int head=0,tail=0;
    q[tail++]=x;
    while(head!=tail){
        x=q[head++];
        size[x]=1;
        son[x]=0;
        for(int i=0;i<(int)G[x].size();i++)
            if(!vis[G[x][i]]&&G[x][i]!=p[x]){
                p[G[x][i]]=x;
                q[tail++]=G[x][i];
            }
    }
    for(int i=tail-1;i;i--){
        x=q[i];
        size[p[x]]+=size[x];
        if(size[x]>size[son[p[x]]])son[p[x]]=x;
    }
    x=q[0];
    while(son[x]&&size[son[x]]>(s>>1))x=son[x];
    return x;
}
int getdis(int x){
    int head=0,tail=0,rt=x;
    q[tail++]=x;
    while(head!=tail){
        x=q[head++];
        s=d[x]=d[p[x]]+1;
        tmp=w[x]=w[p[x]]+val[x];
        modify(0,m,root[rt]);
        size[x]=1;
        for(int i=0;i<(int)G[x].size();i++)
            if(!vis[G[x][i]]&&G[x][i]!=p[x]){
                p[G[x][i]]=x;
                q[tail++]=G[x][i];
            }
    }
    for(int i=tail-1;i;i--){
        x=q[i];
        size[p[x]]+=size[x];
    }
    return d[q[tail-1]];
}
void getans(int x,int v){
    int head=0,tail=0,rt=merge(prefix[pr[x]],suffix[nx[x]]);
    q[tail++]=x;
    while(head!=tail){
        x=q[head++];
        s=L-d[x];
        t=R-d[x];
        if(t<0||s>m)ant[x]=-INF;
        else ant[x]=w[x]-v+query(0,m,rt);
        if(s<=0&&t>=0)ant[x]=max(ant[x],w[x]);
        for(int i=0;i<(int)G[x].size();i++)
            if(!vis[G[x][i]]&&G[x][i]!=p[x]){
                p[G[x][i]]=x;
                q[tail++]=G[x][i];
            }
    }
    for(int i=tail-1;~i;i--){
        x=q[i];
        ant[p[x]]=max(ant[p[x]],ant[x]);
        ans[x]=max(ans[x],ant[x]);
    }
}
void modify(int l,int r,int &rt){
    if(!rt){
        rt=++cnt;
        mx[rt]=-INF;
        lc[rt]=rc[rt]=0;
    }
    mx[rt]=max(mx[rt],tmp);
    if(l==r)return;
    int mid=(l+r)>>1;
    if(s<=mid)modify(l,mid,lc[rt]);
    else modify(mid+1,r,rc[rt]);
}
int merge(int x,int y){
    if(!x||!y)return x|y;
    int z=++cnt;
    mx[z]=max(mx[x],mx[y]);
    lc[z]=merge(lc[x],lc[y]);
    rc[z]=merge(rc[x],rc[y]);
    return z;
}
long long query(int l,int r,int rt){
    if(s<=l&&t>=r)return mx[rt];
    int mid=(l+r)>>1;
    if(t<=mid)return query(l,mid,lc[rt]);
    if(s>mid)return query(mid+1,r,rc[rt]);
    return max(query(l,mid,lc[rt]),query(mid+1,r,rc[rt]));
}

转载于:https://www.cnblogs.com/hzoier/p/9383245.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值