【HNOI2015】开店

题目大意

给出一棵有n个节点的树,每条边有个边长,每个节点有个权值a[i],有q个询问形如”u l r”询问点u到所有权值在l到r之间的点的路径长度和

一眼解法

一个点与多个点之间的距离和,显然我们可以用点分治来做,对于每个分治中心,我们将其所有该层的点都记录下来,那么每层共有n个,最多 nlog2n 个,同时,在我们计算答案时要计算每个分治中心对其父亲的影响。
询问时我们就直接在重心树上跳,沿途记录答案就好了。

贴代码:

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>

#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

typedef long long LL;
typedef double db;

const int N = 150010;

int n,Q,A;
int v[N];
int fa[N],q[N],m,s[N],vis[N],tim,mv[N];
bool bz[N];
int f[N];
struct point{
    int x,v;
    LL s;
}a[N*20];
int r[N],k,l[N];
int ce[N],rt[N*2],u,rmq[N*2][20],fir[N];
LL dis[N];
int h[N],tot;
struct edge{
    int x,len,next;
}e[N*2];

int get(){
    char ch;
    int s=0;
    while(ch=getchar(),ch<'0'||ch>'9');
    s=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
    return s;
}

void inse(int x,int y,int z){
    e[++tot].x=y;
    e[tot].len=z;
    e[tot].next=h[x];
    h[x]=tot;
}

void dfs(int x){
    rt[fir[x]=++u]=x;
    for(int p=h[x];p;p=e[p].next)
        if (!fir[e[p].x]){
            ce[e[p].x]=ce[x]+1;
            dis[e[p].x]=dis[x]+e[p].len;
            dfs(e[p].x);
            rt[++u]=x;
        }
}

void getrmq(){
    fo(i,1,u)rmq[i][0]=rt[i];
    fo(j,1,log(u)/log(2))
        fo(i,1,u-(1<<j)+1)
        if (ce[rmq[i][j-1]]<ce[rmq[i+(1<<(j-1))][j-1]])rmq[i][j]=rmq[i][j-1];
        else rmq[i][j]=rmq[i+(1<<(j-1))][j-1];
}

LL getdis(int x,int y){
    if (!x||!y)return 0;
    LL ans=dis[x]+dis[y];
    x=fir[x],y=fir[y];
    if (x>y)swap(x,y);
    int t=log(y-x+1)/log(2);
    if (ce[rmq[x][t]]<ce[rmq[y-(1<<t)+1][t]])return ans-2*dis[rmq[x][t]];
    return ans-2*dis[rmq[y-(1<<t)+1][t]];
}

bool cmp(point x,point y){
    return x.v<y.v;
}

void bfs(int x){
    vis[q[m=1]=x]=++tim;
    s[x]=1;
    mv[x]=0;
    int head=0;
    while(head<m){
        x=q[++head];
        for(int p=h[x];p;p=e[p].next)
            if (!bz[e[p].x]&&vis[e[p].x]<tim){
                vis[q[++m]=e[p].x]=tim;
                fa[e[p].x]=x;
                s[e[p].x]=1;
                mv[e[p].x]=0;
            }
    }
}

int solve(int x,int last){
    bfs(x);
    int w=0;
    fd(i,m,1){
        x=q[i];
        s[fa[x]]+=s[x];
        mv[fa[x]]=max(mv[fa[x]],s[x]);
        mv[x]=max(mv[x],m-s[x]);
        if (mv[x]*2<=m)w=x;
    }
    bz[w]=1;
    l[w]=k;
    r[w]=k+m;
    fo(i,1,m){
        a[++k].x=q[i];
        a[k].v=v[q[i]];
    }
    sort(a+l[w]+1,a+r[w]+1,cmp);
    fo(i,l[w]+1,l[w]+m){
        x=a[i].x;
        if (last)a[i].s=getdis(last,x);
        a[i].s=getdis(w,x)-a[i].s;
        if (i>l[w]+1)a[i].s=a[i].s+a[i-1].s;
    }
    for(int p=h[w];p;p=e[p].next)
        if (!bz[e[p].x])f[solve(e[p].x,w)]=w;
    return w;
}

int getw(int d,int u,int v){
    int w=u+1,l=d,r=u;
    while(l<=r){
        int mid=(l+r)/2;
        if (a[mid].v<v)l=mid+1;
        else{
            r=mid-1;
            w=mid;
        }
    }
    return w;
}

LL getans(int L,int R,int u){
    int x=u,ls=0;
    LL ans=0;
    while(x){
        int w1=getw(l[x]+1,r[x],L),w2=getw(l[x]+1,r[x],R+1)-1;
        LL v=a[w1-1].s;
        if (w1==l[x]+1)v=0;
        if (w2>=w1)ans+=a[w2].s-v+LL(w2-w1+1-ls)*getdis(x,u);
        ls=max(0,w2-w1+1);
        x=f[x];
    }
    return ans;
}

int main(){
    freopen("shop.in","r",stdin);
    freopen("shop.out","w",stdout);
    n=get(),Q=get(),A=get();
    fo(i,1,n)v[i]=get();
    fo(i,1,n-1){
        int x=get(),y=get(),z=get();
        inse(x,y,z);
        inse(y,x,z);
    }
    dfs(ce[1]=1);
    getrmq();
    solve(1,0);
    LL ans=0;
    fo(i,1,Q){
            int u=get();
            int a=get(),b=get();
            int L=min((a+ans)%A,(b+ans)%A),R=max((a+ans)%A,(b+ans)%A);
            printf("%lld\n",ans=getans(L,R,u));
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值