BZOJ4012: [HNOI2015]开店

41 篇文章 0 订阅
6 篇文章 0 订阅

询问颜色在 [L,R] 区间的点到一个点的距离和,强制在线



这题有两个做法,一个是动态树分治我不会,另一个是树剖
u,v 两点在树上的距离我们可以用 u,v 到根的距离和减2倍 LCA(u,v) 到根的距离和,那对于多个 v 的话,可以将每个v到根的路径上每条边在那个线段树上加一下(边带权打标记的时候乘区间边的权值和), LCA(u,v) 就是 u 到根上路径上的和。
那么将这个做法推广一下,对于每种颜色维护一棵可持久化线段树,一个点一个点添进去,询问的话用R的减 L1 的就好了



code:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 160000;
struct edge
{
    int y,nex;
    ll c;
    edge(){}
    edge(int _y,ll _c,int _nex){y=_y;c=_c;nex=_nex;}
}a[maxn<<1]; int len,fir[maxn];
void ins(int x,int y,ll c){a[++len]=edge(y,c,fir[x]);fir[x]=len;}
struct tree
{
    int lc,rc;
    ll c,flag;
}tr[maxn*120]; int cnt;
int siz[maxn],son[maxn],fa[maxn],top[maxn],w[maxn];
ll fas[maxn],sumn[maxn],sume[maxn],A;
int _to[maxn],n,m,z;
int root[maxn];
struct node
{
    int x,age,k;
    ll s;
}s[maxn];

void clear(int x){tr[x].lc=tr[x].rc=tr[x].c=tr[x].flag=0;}
void merge(int &x,int y)
{
    if(!x){x=y;return ;}
    if(!y)return ;
    merge(tr[x].lc,tr[y].lc);
    merge(tr[x].rc,tr[y].rc);
    tr[x].c+=tr[y].c; tr[x].flag+=tr[y].flag;
    return ;
}
void add(int &x,int l,int r,int lx,int rx)
{
    if(!x)clear(x=++cnt);
    if(lx<=l&&r<=rx)
    {
        tr[x].c+=sume[r]-sume[l-1];
        tr[x].flag++;
        return ;
    }
    int mid=(l+r)>>1;
    if(rx<=mid) add(tr[x].lc,l,mid,lx,rx);
    else if(lx>mid) add(tr[x].rc,mid+1,r,lx,rx);
    else
    {
        add(tr[x].lc,l,mid,lx,mid);
        add(tr[x].rc,mid+1,r,mid+1,rx);
    }
    tr[x].c+=sume[rx]-sume[lx-1];
}
ll Query(int x,int l,int r,int lx,int rx,ll fl)
{
    if(!x)return fl*(sume[rx]-sume[lx-1]);
    if(lx<=l&&r<=rx)return tr[x].c+fl*(sume[r]-sume[l-1]);
    int mid=(l+r)>>1; fl+=tr[x].flag;
    if(rx<=mid)return Query(tr[x].lc,l,mid,lx,rx,fl);
    else if(lx>mid) return Query(tr[x].rc,mid+1,r,lx,rx,fl);
    else return Query(tr[x].lc,l,mid,lx,mid,fl)+
    Query(tr[x].rc,mid+1,r,mid+1,rx,fl);
}
void dfs(int x)
{
    siz[x]=1; son[x]=0;
    for(int k=fir[x];k;k=a[k].nex)
    {
        int y=a[k].y;
        if(y!=fa[x])
        {
            fa[y]=x; fas[y]=a[k].c;
            dfs(y); siz[x]+=siz[y];
            if(siz[y]>siz[son[x]])son[x]=y;
        }
    }
}
void build_(int x,int tp)
{
    w[x]=++z; sume[z]=sume[z-1]+fas[x];  top[x]=tp;
    if(son[x]) build_(son[x],tp);
    for(int k=fir[x];k;k=a[k].nex)
    {
        int y=a[k].y;
        if(y!=fa[x]&&y!=son[x]) build_(y,y);
    }
}
int update(int x,int &rt)
{
    int ret=0;
    while(x)
    {
        int f1=top[x];
        add(rt,1,z,w[f1],w[x]); ret+=sume[w[x]]-sume[w[f1]-1];
        x=fa[f1];
    }
    return ret;
}
int find_l(int x)
{
    int l=1,r=n;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(s[mid].age<x)l=mid+1;
        else r=mid-1;
    }
    return l-1;
}
int find_r(int x)
{
    int l=1,r=n;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(s[mid].age<=x)l=mid+1;
        else r=mid-1;
    }
    return l-1;
}
ll _Ans(int x,int l,int r)
{
    ll s1=0,s2=0;
    while(x)
    {
        int f1=top[x];
        s1+=Query(root[l],1,z,w[f1],w[x],0);
        s2+=Query(root[r],1,z,w[f1],w[x],0);
        x=fa[f1];
    }
    return s2-s1;
}
ll g_ans(int x,int l,int r)
{
    int t1=find_l(l),t2=find_r(r);
    if(t1>t2)return 0;
    ll s1=sumn[s[t2].k]-sumn[s[t1].k]+(t2-t1)*s[_to[x]].s;
    ll s2=2*_Ans(x,s[t1].k,s[t2].k);
    return s1-s2;
}
bool cmp(node x,node y){return x.age<y.age;}

int main()
{
    memset(fir,0,sizeof fir); len=z=cnt=0;

    scanf("%d%d%lld",&n,&m,&A);
    for(int i=1;i<=n;i++) {scanf("%d",&s[i].age); s[i].x=i;}
    sort(s+1,s+n+1,cmp);
    for(int i=1;i<=n;i++)_to[s[i].x]=i;

    for(int i=1;i<n;i++)
    {
        int x,y;ll c; scanf("%d%d%lld",&x,&y,&c);
        ins(x,y,c);ins(y,x,c);
    }
    dfs(1); build_(1,1);

    int nowc=-1,k=0;
    for(int i=1;i<=n;i++)
    {
        if(s[i].age!=nowc)
        {
            if(k)merge(root[k],root[k-1]);
            nowc=s[i].age;k++; sumn[k]=sumn[k-1];
        }
        s[i].k=k;
        s[i].s=update(s[i].x,root[k]);
        sumn[k]+=s[i].s;
    }
    merge(root[k],root[k-1]);

    ll ans=0;
    while(m--)
    {
        int u;
        ll l,r;scanf("%d%lld%lld",&u,&l,&r);
        ll L,R;
        L=min((l+ans)%A,(r+ans)%A);
        R=max((l+ans)%A,(r+ans)%A);
        ans=g_ans(u,L,R);
        printf("%lld\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值