[HNOI2015]开店

题目

显然这个题不强制在线我们就有一个非常优秀的树剖做法

但是现在强制在线使得这个问题变得看起来有些难搞了

于是就变成了动态点分治的板子题了

我们先建一棵点分树,对于每一个分治重心我们存一下当前分治块内所有点到这个点的距离,用一个vector存下来

我们再存一下每个点作为分治重心的时分治块内部所有点上一级分治重心的距离

每次询问我们暴力跳点分树,在vector里二分即可

代码

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define mp std::make_pair
#define pb push_back
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
    char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int maxn=150005;
typedef std::pair<int,int> pii;
struct E{int v,nxt,w;}e[maxn<<1];
int n,num,m,S,rt,cnt,A;
int head[maxn],pre[maxn],vis[maxn],fa[maxn],a[maxn];
int deep[maxn],b[maxn<<1],sum[maxn],mx[maxn],pos[maxn];
int f[maxn<<1][20],lg[maxn<<1];
inline void add(int x,int y,int w) {
    e[++num].v=y;e[num].nxt=head[x];head[x]=num;e[num].w=w;
}
std::vector<int> son[maxn];
std::vector<LL> sg[maxn],sh[maxn];
std::vector<pii> g[maxn],h[maxn];
void getpre(int x) {
    b[++cnt]=x;pos[x]=cnt;
    for(re int i=head[x];i;i=e[i].nxt) {
        if(deep[e[i].v]) continue;
        deep[e[i].v]=deep[x]+1,pre[e[i].v]=pre[x]+e[i].w;
        getpre(e[i].v);b[++cnt]=x;
    }
}
void getroot(int x,int f) {
    sum[x]=1;mx[x]=0;
    for(re int i=head[x];i;i=e[i].nxt) {
        if(vis[e[i].v]||f==e[i].v) continue;
        getroot(e[i].v,x);sum[x]+=sum[e[i].v];
        mx[x]=max(mx[x],sum[e[i].v]);
    }
    mx[x]=max(S-sum[x],mx[x]);
    if(mx[x]<mx[rt]) rt=x;
}
void getdis(int x,int f,int p,int t,int L) {
    g[p].pb(mp(a[x],L));h[t].pb(mp(a[x],L));
    for(re int i=head[x];i;i=e[i].nxt) {
        if(vis[e[i].v]||f==e[i].v) continue;
        getdis(e[i].v,x,p,t,L+e[i].w); 
    }
}
void solve(int x) {
    std::sort(g[x].begin(),g[x].end());
    sg[x].pb(g[x][0].second);
    for(re int i=1;i<g[x].size();i++) 
        sg[x].pb(sg[x][i-1]+g[x][i].second);
}
void dfs(int x) {
    vis[x]=1;h[x].pb(mp(a[x],0));
    for(re int i=head[x];i;i=e[i].nxt) {
        if(vis[e[i].v]) continue;
        rt=0,S=sum[e[i].v],getroot(e[i].v,0);fa[rt]=x;
        getdis(e[i].v,0,rt,x,e[i].w);
        solve(rt);son[x].pb(rt);
    }
    std::sort(h[x].begin(),h[x].end());
    sh[x].pb(h[x][0].second);
    for(re int i=1;i<h[x].size();i++) 
        sh[x].pb(sh[x][i-1]+h[x][i].second);
    for(re int i=0;i<son[x].size();i++) dfs(son[x][i]);
}
inline void find(int x,int t,int& tot,LL &ans) {
    int l=0,r=g[x].size()-1,c=-1;
    while(l<=r) {
        int mid=l+r>>1;
        if(g[x][mid].first<=t) l=mid+1,c=mid;
            else r=mid-1;
    }
    if(c!=-1) tot-=c+1,ans-=sg[x][c];
}
inline int LCA(int x,int y) {
    x=pos[x],y=pos[y];
    if(x>y) std::swap(x,y);
    int k=lg[y-x+1];
    if(deep[f[x][k]]<deep[f[y-(1<<k)+1][k]]) return f[x][k];
    return f[y-(1<<k)+1][k];
}
inline int dis(int x,int y) {return pre[x]+pre[y]-2*pre[LCA(x,y)];}
inline LL ask(int x,int t) {
    int p=0,k=x;LL ans=0;
    while(x) {
        int tot=0,c=-1,l=0,r=h[x].size()-1;
        while(l<=r) {
            int mid=l+r>>1;
            if(h[x][mid].first<=t) l=mid+1,c=mid;
                else r=mid-1;
        }
        if(c!=-1) tot=c+1,ans+=sh[x][c];
        if(p) find(p,t,tot,ans);
        ans+=1ll*tot*dis(x,k);
        p=x;x=fa[x];
    }
    return ans;
}
int main() {
    n=read(),m=read(),A=read();
    for(re int i=1;i<=n;i++) a[i]=read();
    for(re int x,y,w,i=1;i<n;i++) {
        x=read(),y=read(),w=read();
        add(x,y,w),add(y,x,w);
    }
    deep[1]=1;getpre(1);
    for(re int i=1;i<=2*n;i++) f[i][0]=b[i];
    for(re int i=2;i<=2*n;i++) lg[i]=lg[i>>1]+1;
    for(re int j=1;j<=lg[2*n];j++)
        for(re int i=1;i+(1<<j)-1<=2*n;i++)
            if(deep[f[i][j-1]]<deep[f[i+(1<<(j-1))][j-1]]) f[i][j]=f[i][j-1];
                else f[i][j]=f[i+(1<<(j-1))][j-1];
    mx[0]=n+1,S=n,rt=0,getroot(1,0);dfs(rt);
    LL lst=0;int u,x,y,l,r;
    while(m--) {
        u=read(),x=read(),y=read();
        l=(1ll*x+lst)%A,r=(1ll*y+lst)%A;
        if(l>r) std::swap(l,r);
        printf("%lld\n",lst=ask(u,r)-ask(u,l-1));
    }
    return 0;
}

转载于:https://www.cnblogs.com/asuldb/p/10864567.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值