洛谷 P3241 [HNOI2015]开店 动态树分治

题目描述

风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。
这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 n n n 个地方,编号为 1 1 1 n n n n − 1 n-1 n1 条带权的边连接起来。每个地方都住着一个妖怪,其中第 i i i 个地方的妖怪年龄是 x i x_i xi

妖怪都是些比较喜欢安静的家伙,所以它们并不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 3 3 3 。妖怪和人一样,兴趣点随着年龄的变化自然就会变化,比如我们的 18 18 18 岁少女幽香和八云紫就比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以幽香打算选择一个地方 u u u u u u 为编号),然后在 u u u 开一家面向年龄在 L L L R R R 之间(即年龄大于等于 L L L 小于等于 R R R)的妖怪的店。

也有可能 u u u 这个地方离这些妖怪比较远,于是幽香就想要知道所有年龄在 L L L R R R 之间的妖怪,到点 u u u 的距离的和是多少(妖怪到 u u u 的距离是该妖怪所在地方到 u u u 的路径上的边的权之和),幽香把这个称为这个开店方案的方便值。

幽香她们还没有决定要把店开在哪里,八云紫倒是准备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

输入输出格式

输入格式:
第一行三个用空格分开的数 n , Q n,Q n,Q A A A,表示树的大小、开店的方案个数和妖怪的年龄上限。
第二行nn 个用空格分开的数 x 1 , x 2 , … , x n x_1,x_2,\ldots,x_n x1,x2,,xn x i x_i xi表示第 i i i 个地点妖怪的年龄,满足 0 ≤ x i &lt; A 0\le x_i\lt A 0xi<A 。(年龄是可以为 0 0 0 的,例如刚出生的妖怪的年龄为 0 0 0。)

接下来 n − 1 n-1 n1 行,每行三个用空格分开的数 a a a b b b c c c ,表示树上的顶点 a a a b b b 之间有一条权为 c ( 1 ≤ c ≤ 1000 ) c(1\le c\le1000) c(1c1000) 的边,aa 和bb 是顶点编号。

接下来 Q Q Q 行,每行三个用空格分开的数 u , a , b u,a,b u,a,b

对于这 Q Q Q 行的每一行,用 a , b , A a,b,A a,b,A 计算出 L L L R R R ,表示询问”在地方 u u u 开店,面向妖怪的年龄区间为 [ L , R ] [L,R] [L,R] 的方案的方便值是多少。

对于其中第 1 1 1 行, L L L R R R 的计算方法为: L = m i n ( a % A , b % A ) L=min(a \% A,b \%A) L=min(a%A,b%A), R = m a x ( a % A , b % A ) R=max(a \% A,b \%A) R=max(a%A,b%A)

对于第 2 2 2 到第 Q Q Q 行,假设前一行得到的方便值为 a n s ans ans,那么当前行的 L L L R R R 计算方法为:
L = m i n ( ( a + a n s ) % A , ( b + a n s ) % A ) L=min((a+ans) \% A,(b+ans) \%A) L=min((a+ans)%A,(b+ans)%A), R = m a x ( ( a + a n s ) % A , ( b + a n s ) % A ) R=max((a+ans) \% A,(b+ans) \%A) R=max((a+ans)%A,(b+ans)%A)

输出格式:
对于每个方案,输出一行表示方便值。

输入输出样例

输入样例#1:
10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4
输出样例#1:
1603
957
7161
9466
3232
5223
1879
1669
1282
0

说明
满足 n ≤ 1.5 ∗ 1 0 5 , Q ≤ 2 ∗ 1 0 5 n\le1.5*10^5,Q\le2*10^5 n1.5105,Q2105。对于所有数据,满足 A &lt; = 1 0 9 A&lt;=10^9 A<=109

分析:
我们可以对树进行动态树分治,每个点维护当前分治中心所有节点的年龄,到分治中心的距离。存进一个vector然后再排序跑前缀和。顺便维护到父亲分治中心的同样的东西。
对于一个询问。当前分治中心父亲除了当前块外的所有连通块的 [ L , R ] [L,R] [L,R]的距离和统计出来,再把点的个数算出来,乘上父亲分治中心到询问点距离即可。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#define LL long long

const int maxn=150007;

using namespace std;

int n,m,maxa,T,cnt,u,x,y,w;
LL ans;
int ls[maxn],a[maxn],b[maxn],f[maxn][20],dep[maxn],d[maxn];

struct edge{
    int y,w,next;
}g[maxn*2];

struct rec{
    int num,x;
    LL y;
};

bool operator <(rec a,rec b)
{
    return a.num<b.num;
}

struct line{
    vector <rec> data[maxn];
    void change(int p,int x,int k)
    {
    	data[p].push_back((rec){x,1,k});
    }   
    
    int find(int p,int x)
    {
    	int l=0,r=data[p].size()-1,ans=-1;
    	while (l<=r)
    	{
    		int mid=(l+r)/2;
    		if (data[p][mid].num<=x) l=mid+1,ans=mid;
    		                    else r=mid-1;
    	}
    	return ans;
    }
    rec query(int p,int l,int r)
    {	
    	l=find(p,l-1),r=find(p,r);
    	if (r==-1) return (rec){0,0,0};
    	if (l==-1) return data[p][r];
    	return (rec){0,data[p][r].x-data[p][l].x,(LL)data[p][r].y-(LL)data[p][l].y};
    }
}A,B;

void add(int x,int y,int w)
{
    g[++cnt]=(edge){y,w,ls[x]};
    ls[x]=cnt;
}

void dfs(int x,int fa)
{
    f[x][0]=fa;
    for (int i=ls[x];i>0;i=g[i].next)
    {
        int y=g[i].y;
        if (y==fa) continue;
        dep[y]=dep[x]+1;
        d[y]=d[x]+g[i].w;
        dfs(y,x);
    }
}
int getlca(int x,int y)
{
    if (dep[x]>dep[y]) swap(x,y);
    int d=dep[y]-dep[x],k=19,t=1<<k;
    while (d)
    {
        if (d>=t) d-=t,y=f[y][k];
        t/=2,k--;
    }
    if (x==y) return x;
    k=19;
    while (k>=0)
    {
        if (f[x][k]!=f[y][k])
        {
            x=f[x][k];
            y=f[y][k];
        }
        k--;
    }
    return f[x][0];
}
 
int getdis(int x,int y)
{
    return d[x]+d[y]-2*d[getlca(x,y)];
}

struct tree{
    int fa[maxn],f[maxn],size[maxn],vis[maxn],dis[maxn];
    int root,sum;
    queue <int> q;
    void findroot(int x,int F)
    {
        size[x]=1;
        f[x]=0;
        for (int i=ls[x];i>0;i=g[i].next)
        {
            int y=g[i].y;
            if ((y==F) || (vis[y])) continue;
            findroot(y,x);
            size[x]+=size[y];
            f[x]=max(f[x],size[y]);
        }
        f[x]=max(f[x],sum-size[x]);
        if ((f[x]<f[root]) || (!root)) root=x;
    } 
    void dfs(int x,int F)
    {
        size[x]=1;
        A.change(root,a[x],dis[x]);
        B.change(root,a[x],getdis(fa[root],x));
        for (int i=ls[x];i>0;i=g[i].next)
        {
            int y=g[i].y;
            if ((y==F) || (vis[y])) continue;
            dis[y]=dis[x]+g[i].w;
            dfs(y,x);
            size[x]+=size[y];
            if (x==root)
            {
                fa[y]=x;
                q.push(y);
            }
        }
    }
    void build()
    {
        q.push(1);
        size[1]=n;
        while (!q.empty())
        {
            int x=q.front();
            q.pop();
            sum=size[x];
            root=0;
            findroot(x,0);
            fa[root]=fa[x];
            dis[root]=0;
            dfs(root,0);
            vis[root]=1;
            sort(A.data[root].begin(),A.data[root].end());
            sort(B.data[root].begin(),B.data[root].end());
            for (int i=1;i<A.data[root].size();i++) A.data[root][i].x+=A.data[root][i-1].x;
            for (int i=1;i<A.data[root].size();i++) A.data[root][i].y+=A.data[root][i-1].y;
            for (int i=1;i<B.data[root].size();i++) B.data[root][i].x+=B.data[root][i-1].x;
            for (int i=1;i<B.data[root].size();i++) B.data[root][i].y+=B.data[root][i-1].y;
        }
    }
    LL getans(int x,int l,int r)
    {
        LL ans=0;
        rec d=A.query(x,l,r);
        ans+=d.y;
        for (int i=x;fa[i];i=fa[i])
        {
            d=A.query(fa[i],l,r);
            ans+=d.y+(LL)d.x*(LL)getdis(x,fa[i]);
            d=B.query(i,l,r);
            ans-=d.y+(LL)d.x*(LL)getdis(x,fa[i]);
        }
        return ans;
    }
}Tree;

int main()
{
    scanf("%d%d%d",&n,&T,&maxa);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    m=unique(b+1,b+n+1)-b-1;
    for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+m+1,a[i])-b;
    for (int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&w);
        add(x,y,w);
        add(y,x,w);
    }
    dfs(1,0);
    for (int j=1;j<20;j++)
    {
        for (int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1];
    }	
    Tree.build();	
    for (int i=1;i<=T;i++)
    {
        LL L,R;
        if (i==T)
        {
            L++;
        }
        scanf("%d%lld%lld",&u,&L,&R);
        int l=min((LL)(L+ans)%(LL)maxa,(LL)(R+ans)%(LL)maxa);
        int r=max((LL)(L+ans)%(LL)maxa,(LL)(R+ans)%(LL)maxa);
        l=lower_bound(b+1,b+m+1,l)-b;
        r=upper_bound(b+1,b+m+1,r)-b-1;
        ans=Tree.getans(u,l,r);
        printf("%lld\n",ans);
    }
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值