[BZOJ]4012: [HNOI2015]开店 树链剖分+主席树(线段树合并)

Description

风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 n个地方,编号为 1 到 n,被 n-1 条带权的边连接起来。每个地方都住着一个妖怪,其中第 i 个地方的妖怪年龄是 x_i。妖怪都是些比较喜欢安静的家伙,所以它们并不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 3。妖怪和人一样,兴趣点随着年龄的变化自然就会变化,比如我们的 18 岁少女幽香和八云紫就比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以幽香打算选择一个地方 u(u为编号),然后在 u开一家面向年龄在 L到R 之间(即年龄大于等于 L、小于等于 R)的妖怪的店。也有可能 u这个地方离这些妖怪比较远,于是幽香就想要知道所有年龄在 L 到 R 之间的妖怪,到点 u 的距离的和是多少(妖怪到 u 的距离是该妖怪所在地方到 u 的路径上的边的权之和) ,幽香把这个称为这个开店方案的方便值。幽香她们还没有决定要把店开在哪里,八云紫倒是准备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

题解:

好题!考虑两点间计算距离的公式: dis[x]+dis[y]2×dis[lca(x,y)] ,那么对于年龄在 [L,R] 区间的妖怪,统计一下 [1,i] 区间的妖怪个数和这些妖怪到根的距离之和的前缀和是很容易的,所以关键是所有的 lca 到根的距离之和怎么求。我们对年龄从小到大排序、离散化,对于每个妖怪,把它到根的路径上的边都+1,表示这条边被经过了1次,那么对于每次的点 u ,它与所有点的lca到根的距离之和就是 u 到根上的边的边权×次数之和,由于规定区间 [L,R] ,所以要可持久化线段树。但是要注意这个主席树是改段的,开空间要大(具体不太会算),然后 lazy 标记也不能下传,不然会有各种各样的问题,需要标记永久化。我看的是fyc的博客

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=150010;
const int inf=2147483647;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
LL A;
int n,Q,a[Maxn];
struct Age{int x,id;}b[Maxn];
bool cmp(Age a,Age b){return a.x<b.x;}
struct Edge{int y,d,next;}e[Maxn<<1];
int last[Maxn],len=0;
void ins(int x,int y,int d){int t=++len;e[t].y=y;e[t].d=d;e[t].next=last[x];last[x]=t;}
int dep[Maxn],tot[Maxn],son[Maxn],ys[Maxn],dfn=0,fa[Maxn],top[Maxn],val[Maxn];
void dfs1(int x,int f)
{
    dep[x]=dep[f]+1;fa[x]=f;tot[x]=1;son[x]=0;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(y!=f)
        {
            dfs1(y,x);
            tot[x]+=tot[y];
            son[x]=(tot[son[x]]<tot[y])?y:son[x];
        }
    }
}
LL Sum[Maxn],S1[Maxn],S2[Maxn],dis[Maxn];
void dfs2(int x,int tp,int d,LL Dis)
{
    ys[x]=++dfn;top[x]=tp;val[dfn]=d;S2[a[x]]+=(dis[x]=Dis);
    if(son[x])
    {
        int o;
        for(int i=last[x];i;i=e[i].next)
        if(e[i].y==son[x]){o=e[i].d;break;}
        dfs2(son[x],tp,o,Dis+o);
    }
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(y!=fa[x]&&y!=son[x])dfs2(y,y,e[i].d,Dis+e[i].d);
    }
}
int root[Maxn],lc[Maxn*75],rc[Maxn*75],Cnt=0;
LL s[Maxn*75],lazy[Maxn*75];
void insert(int &u,int l,int r,int ll,int rr)//[ll,rr]为当前需要修改的区间 
{
    if(!u)u=++Cnt;
    s[u]+=Sum[rr]-Sum[ll-1];
    if(l==ll&&r==rr){lazy[u]++;return;}
    int mid=l+r>>1;
    if(rr<=mid)insert(lc[u],l,mid,ll,rr);
    else if(ll>mid)insert(rc[u],mid+1,r,ll,rr);
    else insert(lc[u],l,mid,ll,mid),insert(rc[u],mid+1,r,mid+1,rr);
}
void merge(int &u1,int u2)
{
    if(!u1){u1=u2;return;}
    if(!u2)return;
    s[u1]+=s[u2];lazy[u1]+=lazy[u2];
    merge(lc[u1],lc[u2]);merge(rc[u1],rc[u2]);
}
LL query(int rt1,int rt2,int l,int r,int ll,int rr,LL tag)
{
    if(l==ll&&r==rr)return s[rt1]-s[rt2]+tag*(Sum[r]-Sum[l-1]);
    int mid=l+r>>1;
    if(rr<=mid)return query(lc[rt1],lc[rt2],l,mid,ll,rr,tag+lazy[rt1]-lazy[rt2]);
    else if(ll>mid)return query(rc[rt1],rc[rt2],mid+1,r,ll,rr,tag+lazy[rt1]-lazy[rt2]);
    else return query(lc[rt1],lc[rt2],l,mid,ll,mid,tag+lazy[rt1]-lazy[rt2])+
    query(rc[rt1],rc[rt2],mid+1,r,mid+1,rr,tag+lazy[rt1]-lazy[rt2]);
}
void work(int x,int y,int rt)
{
    int tx=top[x],ty=top[y];
    while(tx!=ty)
    {
        if(dep[tx]<dep[ty])swap(x,y),swap(tx,ty);
        insert(rt,1,n,ys[tx],ys[x]);
        x=fa[tx];tx=top[x];
    }
    if(x==y)return;
    if(dep[x]<dep[y])swap(x,y);
    insert(rt,1,n,ys[son[y]],ys[x]);
}
int Find1(int x)//找后继 可以等于 
{
    int l=1,r=n;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(b[mid].x>=x)r=mid-1;
        else l=mid+1;
    }return a[b[r+1].id];
}
int Find2(int x)//找前驱 可以等于 
{
    int l=1,r=n;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(b[mid].x<=x)l=mid+1;
        else r=mid-1;
    }return a[b[l-1].id];
}
LL solve(int u,int x,int y)
{
    x=Find1(x);y=Find2(y);
    if(x>y)return 0;
    LL re=0;
    int X=x,Y=y,rt1=root[Y],rt2=root[X-1];
    x=1,y=u;int tx=top[x],ty=top[y];
    while(tx!=ty)
    {
        if(dep[tx]<dep[ty])swap(x,y),swap(tx,ty);
        re+=query(rt1,rt2,1,n,ys[tx],ys[x],0);
        x=fa[tx];tx=top[x];
    }
    if(x==y)return (S1[Y]-S1[X-1])*dis[u]+S2[Y]-S2[X-1]-2LL*re;
    if(dep[x]<dep[y])swap(x,y);
    return (S1[Y]-S1[X-1])*dis[u]+S2[Y]-S2[X-1]-2LL*(re+query(rt1,rt2,1,n,ys[son[y]],ys[x],0));
}
int main()
{
    n=read(),Q=read(),A=read();
    for(int i=1;i<=n;i++)b[i].x=read(),b[i].id=i;
    sort(b+1,b+1+n,cmp);
    int cnt=0;b[0].x=-1;
    for(int i=1;i<=n;i++)a[b[i].id]=((b[i].x!=b[i-1].x)?(++cnt):cnt);
    b[0].id=n+2;b[n+1].id=n+1;a[n+1]=cnt+1;a[n+2]=0;
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read(),d=read();
        ins(x,y,d);ins(y,x,d);
    }
    dep[0]=0;dfs1(1,0); dfs2(1,1,0,0);
    for(int i=1;i<=n;i++)S1[a[i]]++;
    Sum[0]=0;for(int i=1;i<=n;i++)Sum[i]=Sum[i-1]+(LL)(val[i]),S1[i]+=S1[i-1],S2[i]+=S2[i-1];
    int now=1,o=1;root[0]=0;
    while(now<=n)
    {
        root[o]=++Cnt;
        work(1,b[now].id,root[o]);
        while(now<n&&b[now].x==b[now+1].x)work(1,b[++now].id,root[o]);
        now++;
        merge(root[o],root[o-1]);
        o++;
    }
    LL ans=0;
    while(Q--)
    {
        int u=read(),l,r;
        LL _a=read(),_b=read();
        l=min((_a+ans)%A,(_b+ans)%A);r=max((_a+ans)%A,(_b+ans)%A);
        ans=solve(u,l,r);
        printf("%lld\n",ans);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值