zoukankan      html  css  js  c++  java
  • BZOJ4012[HNOI2015]开店——树链剖分+可持久化线段树/动态点分治+vector

    题目描述

     风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到

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

    输入

     第一行三个用空格分开的数 n、Q和A,表示树的大小、开店的方案个数和妖

    怪的年龄上限。 
    第二行n个用空格分开的数 x_1、x_2、…、x_n,x_i 表示第i 个地点妖怪的年
    龄,满足0<=x_i<A。(年龄是可以为 0的,例如刚出生的妖怪的年龄为 0。) 
    接下来 n-1 行,每行三个用空格分开的数 a、b、c,表示树上的顶点 a 和 b 之
    间有一条权为c(1 <= c <= 1000)的边,a和b 是顶点编号。 
    接下来Q行,每行三个用空格分开的数 u、 a、 b。对于这 Q行的每一行,用 a、
    b、A计算出 L和R,表示询问“在地方 u开店,面向妖怪的年龄区间为[L,R]的方
    案的方便值是多少”。对于其中第 1 行,L 和 R 的计算方法为:L=min(a%A,b%A), 
    R=max(a%A,b%A)。对于第 2到第 Q行,假设前一行得到的方便值为 ans,那么当
    前行的 L 和 R 计算方法为: L=min((a+ans)%A,(b+ans)%A), 
    R=max((a+ans)%A,(b+ans)%A)。 

    输出

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

    样例输入

    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

    样例输出

    1603
    957
    7161
    9466
    3232
    5223
    1879
    1669
    1282
    0

    提示

     满足 n<=150000,Q<=200000。对于所有数据,满足 A<=10^9

    树链剖分+可持久化线段树

    这道题和BZOJ3626比较相似.首先考虑没有年龄限制,就是求一些点和一个点x的距离和.答案就是Σdep[i]+dep[x]-2*dep[lca],dep[i]可以处理前缀和,再求出点数乘上dep[x]就就得到了前半部分答案,最后lca深度和怎么求?如果单独求两点x,y的lca深度可以将x到根路径上所有边权值+1(每条边权值相同情况下)然后再求y到根路径上边权和就好了。那么一些点和x的lca深度和也可以用同样的求法,按树剖序建线段树,边权下传到点上,每次跳重链修改和查询。因为这道题边权不同,所以每次给边权+1表示这条边对答案贡献次数+1,维护一个永久化的标记(就是不下传的标记)表示区间要对答案贡献几次,再每个点维护一个贡献和表示这个点代表的区间对答案的总贡献(即这个点在线段树中子树中所有点的永久化标记*区间权值和之和)就行了。那么现在有了年龄限制显然一棵线段树是不行的,因此要把年龄离散化后从小到大每种年龄建一棵可持久化线段树,求年龄区间只要把对应可持久化线段树相减剩下的就只是对应年龄区间的点到根的标记。注意要开longlong。

    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    ll ans;
    int n,m;
    int tot;
    int cnt;
    int num;
    int A,k;
    int l,r;
    int x,y,z;
    int p[150010];
    int h[150010];
    int f[150010];
    int g[150010];
    int q[150010];
    int v[150010];
    int to[300010];
    ll s[20000010];
    ll sum[150010];
    int dep[150010];
    int t[20000010];
    int son[150010];
    int top[150010];
    int val[300010];
    int head[150010];
    int size[150010];
    int next[300010];
    int root[150010];
    int ls[20000010];
    int rs[20000010];
    ll total[150010];
    struct node
    {
        int x;
        int id;
    }a[150010];
    bool cmp(node a,node b)
    {
        return a.x<b.x;
    }
    void add(int x,int y,int z)
    {
        tot++;
        next[tot]=head[x];
        head[x]=tot;
        to[tot]=y;
        val[tot]=z;
    }
    void dfs(int x)
    {
        size[x]=1;
        for(int i=head[x];i;i=next[i])
        {
            if(to[i]!=f[x])
            {
                dep[to[i]]=dep[x]+val[i];
                f[to[i]]=x;
                v[to[i]]=val[i];
                dfs(to[i]);
                size[x]+=size[to[i]];
                if(size[to[i]]>size[son[x]])
                {
                    son[x]=to[i];
                }
            }
        }
    }
    void dfs2(int x,int tp)
    {
        top[x]=tp;
        p[x]=++num;
        q[num]=x;
        if(son[x])
        {
            dfs2(son[x],tp);
        }
        for(int i=head[x];i;i=next[i])
        {
            if(to[i]!=f[x]&&to[i]!=son[x])
            {
                dfs2(to[i],to[i]);
            }
        }
    }
    void pushup(int rt,int l,int r)
    {
        s[rt]=s[ls[rt]]+s[rs[rt]]+t[rt]*(sum[r]-sum[l-1]);
    }
    void change(int &rt,int pre,int l,int r,int L,int R)
    {
        rt=++cnt;
        ls[rt]=ls[pre];
        rs[rt]=rs[pre];
        s[rt]=s[pre];
        t[rt]=t[pre];
        if(L<=l&&r<=R)
        {
            t[rt]++;
            s[rt]+=sum[r]-sum[l-1];
            return ;
        }
        int mid=(l+r)>>1;
        if(L<=mid)
        {
            change(ls[rt],ls[pre],l,mid,L,R);
        }
        if(R>mid)
        {
            change(rs[rt],rs[pre],mid+1,r,L,R);
        }
        pushup(rt,l,r);
    }
    ll query(int rt,int l,int r,int L,int R)
    {
        if(L<=l&&r<=R)
        {
            return s[rt];
        }
        ll res=1ll*t[rt]*(sum[min(r,R)]-sum[max(l,L)-1]);
        int mid=(l+r)>>1;
        if(L<=mid)
        {
            res+=query(ls[rt],l,mid,L,R);
        }
        if(R>mid)
        {
            res+=query(rs[rt],mid+1,r,L,R);
        }
        return res;
    }
    void updata(int x,int rt)
    {
        while(top[x]!=1)
        {
            change(root[rt],root[rt],1,n,p[top[x]],p[x]);
            x=f[top[x]];
        }
        change(root[rt],root[rt],1,n,1,p[x]);
    }
    ll find(int x,int l,int r)
    {
        ll res=0;
        while(top[x]!=1)
        {
            res+=query(root[r],1,n,p[top[x]],p[x])-query(root[l],1,n,p[top[x]],p[x]);
            x=f[top[x]];
        }
        res+=query(root[r],1,n,1,p[x])-query(root[l],1,n,1,p[x]);
        return res;
    }
    int main()
    {
        scanf("%d%d%d",&n,&m,&A);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i].x);
            h[i]=a[i].x;
            a[i].id=i;
        }
        sort(h+1,h+n+1);
        k=unique(h+1,h+1+n)-h-1;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);
        }
        dfs(1);
        dfs2(1,1);
        sort(a+1,a+1+n,cmp);
        for(int i=1;i<=n;i++)
        {
            sum[i]=sum[i-1]+v[q[i]];
        }
        for(int i=1;i<=n;i++)
        {
            int fx=lower_bound(h+1,h+1+k,a[i].x)-h;
            g[fx]=i;
            if(fx==(lower_bound(h+1,h+1+k,a[i-1].x)-h))
            {
                total[fx]=total[fx]+1ll*dep[a[i].id];
            }
            else
            {
                total[fx]=total[fx-1]+1ll*dep[a[i].id];
                root[fx]=root[fx-1];
            }
            updata(a[i].id,fx);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&z,&x,&y);
            l=min((x+ans)%A,(y+ans)%A);
            r=max((x+ans)%A,(y+ans)%A);
            l=upper_bound(h+1,h+1+k,l-1)-h-1;
            r=upper_bound(h+1,h+1+k,r)-h-1;
            ans=total[r]-total[l]+1ll*(g[r]-g[l])*dep[z]-2*find(z,l,r);
            printf("%lld
    ",ans);
        }
    }

    动态点分治+vector

    这道题用动态点分治做思路就很简单了,考虑对于单次询问如何用点分治解决。假设查询点为x,对于分治联通块中包含x的分治中心,统计联通块内点权在[L,R]之间的点到分治中心的距离和及点数,这些点到x的距离就是他们先到分治中心的距离+分治中心到x的距离。但与x位于分治中心同一子树中的点不能这样算,所以要容斥减掉这一部分的答案,也就是还要统计与x位于同一子树的这个联通块中所有点到分治中心的距离及点数。那么转换到点分树上就需要在每个点维护这个点在点分树上的子树中各个点权的点到这个点的距离和及点数与这个点在点分树上的子树中各个点权的点到这个点的父节点的距离和及点数。查询时同样往根爬容斥一下即可。对于维护这两种信息大家可能会想用线段树,但线段树的内存这道题显然开不下,不过可以发现所有询问都是在添加信息完成之后再处理,且对信息没有修改,所以可以每个点开两个vector存信息然后再排序。查询时直接在vector上二分查找即可。注意在vector两端开哨兵节点防止越界。

    #include<map>
    #include<set>
    #include<queue>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<bitset>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    #define pr pair<int,ll>
    using namespace std;
    inline char _read()
    {
        static char buf[100000],*p1=buf,*p2=buf;
        return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
    }
    inline int read()
    {
        int x=0,f=1;char ch=_read();
        while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=_read();}
        while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=_read();}
        return x*f;
    }
    ll ans;
    int l,r;
    int a,b;
    int tot;
    int dfn;
    int num;
    int cnt;
    int rot;
    int n,m,A;
    int x,y,z;
    int f[150010];
    int s[150010];
    int h[150010];
    int v[150010];
    int to[300010];
    int lg[300010];
    int mx[150010];
    ll dep[150010];
    int vis[150010];
    int val[300010];
    int head[150010];
    int next[300010];
    int size[150010];
    int g[19][300010];
    vector<ll>sum[150010];
    vector<pr>root[150010];
    vector<ll>fsum[150010];
    vector<int>lev[150010];
    vector<pr>froot[150010];
    vector<int>flev[150010];
    inline void add(int x,int y,int z)
    {
    	tot++;
    	next[tot]=head[x];
    	head[x]=tot;
    	to[tot]=y;
    	val[tot]=z;
    }
    inline void dfs(int x,int fa)
    {
    	g[0][++dfn]=dep[x];
    	s[x]=dfn;
    	for(int i=head[x];i;i=next[i])
    	{
    		if(to[i]!=fa)
    		{
    			dep[to[i]]=dep[x]+1ll*val[i];
    			dfs(to[i],x);
    			g[0][++dfn]=dep[x];
    		}
    	}
    }
    inline void getroot(int x,int fa) 
    {
    	size[x]=1;
    	mx[x]=0;
    	for(int i=head[x];i;i=next[i])
    	{
    		if(!vis[to[i]]&&to[i]!=fa)
    		{
    			getroot(to[i],x);
    			size[x]+=size[to[i]];
    			mx[x]=max(mx[x],size[to[i]]);
    		}
    	}
    	mx[x]=max(mx[x],num-size[x]);
    	if(mx[x]<mx[rot])
    	{
    		rot=x;
    	}
    }
    inline void partation(int x)
    {
    	vis[x]=1;
    	for(int i=head[x];i;i=next[i])
    	{
    		if(!vis[to[i]])
    		{
    			num=size[to[i]];
    			rot=0;
    			getroot(to[i],0);
    			f[rot]=x;
    			partation(rot);
    		}
    	}
    }
    inline int lca(int x,int y)
    {
    	x=s[x];
    	y=s[y];
    	if(x>y)
    	{
    		swap(x,y);
    	}
    	int len=lg[y-x+1];
    	return min(g[len][x],g[len][y-(1<<len)+1]);
    }
    inline ll dis(int x,int y)
    {
    	return dep[x]+dep[y]-2ll*lca(x,y);
    }
    inline void insert(int x,int val)
    {
    	for(int i=x;i;i=f[i])
    	{
    		root[i].push_back(make_pair(val,dis(x,i)));
    		if(f[i])
    		{
    			froot[i].push_back(make_pair(val,dis(x,f[i])));
    		}
    	}
    }
    inline ll query(int x,int L,int R)
    {
    	ll res=0;
    	int l,r;
    	for(int i=x;i;i=f[i])
    	{
    		l=upper_bound(lev[i].begin(),lev[i].end(),L-1)-lev[i].begin()-1;
    		r=upper_bound(lev[i].begin(),lev[i].end(),R)-lev[i].begin()-1;
    		res+=sum[i][r]-sum[i][l];
    		if(f[i])
    		{
    			l=upper_bound(flev[i].begin(),flev[i].end(),L-1)-flev[i].begin()-1;
    			r=upper_bound(flev[i].begin(),flev[i].end(),R)-flev[i].begin()-1;
    			res-=fsum[i][r]-fsum[i][l];
    		}
    	}
    	for(int i=x;f[i];i=f[i])
    	{
    		r=(upper_bound(lev[f[i]].begin(),lev[f[i]].end(),R)-lev[f[i]].begin())-(upper_bound(lev[f[i]].begin(),lev[f[i]].end(),L-1)-lev[f[i]].begin());
    		l=(upper_bound(flev[i].begin(),flev[i].end(),R)-flev[i].begin())-(upper_bound(flev[i].begin(),flev[i].end(),L-1)-flev[i].begin());
    		res+=dis(x,f[i])*(r-l);
    	}
    	return res;
    }
    int main()
    {
    	n=read();m=read();A=read();
    	for(int i=1;i<=n;i++)
    	{
    		v[i]=read();
    		h[i]=v[i];
    	}
    	sort(h+1,h+1+n);
    	for(int i=1;i<=n;i++)
    	{
    		v[i]=lower_bound(h+1,h+1+n,v[i])-h;
    	}
    	for(int i=1;i<n;i++)
    	{
    		x=read();y=read();z=read();
    		add(x,y,z);
    		add(y,x,z);
    	}
    	dfs(1,0);
    	mx[0]=1<<30;
    	num=n;
    	for(int i=2;i<=dfn;i++)
    	{
    		lg[i]=lg[i>>1]+1;
    	}
    	for(int j=1;(1<<j)<=dfn;j++)
    	{
    		for(int i=1;i+(1<<j)-1<=dfn;i++)
    		{
    			g[j][i]=min(g[j-1][i],g[j-1][i+(1<<(j-1))]);
    		}
    	}
    	getroot(1,0);
    	partation(rot);
    	for(int i=1;i<=n;i++)
    	{
    		insert(i,v[i]);
    	}
    	for(int i=1;i<=n;i++)
    	{
    		sort(root[i].begin(),root[i].end());
    		int len=root[i].size();
    		ll res=0;
    		lev[i].push_back(0);
    		sum[i].push_back(0);
    		for(int j=0;j<len;j++)
    		{
    			res+=root[i][j].second;
    			lev[i].push_back(root[i][j].first);
    			sum[i].push_back(res);
    		}
    		lev[i].push_back(n+1);
    		sum[i].push_back(1<<30);
    		sort(froot[i].begin(),froot[i].end());
    		len=froot[i].size();
    		res=0;
    		flev[i].push_back(0);
    		fsum[i].push_back(0);
    		for(int j=0;j<len;j++)
    		{
    			res+=froot[i][j].second;
    			flev[i].push_back(froot[i][j].first);
    			fsum[i].push_back(res);
    		}
    		flev[i].push_back(n+1);
    		fsum[i].push_back(1<<30);
    	}
    	while(m--)
    	{
    		x=read();a=read();b=read();
    		l=min((1ll*a+ans)%A,(1ll*b+ans)%A);
    		r=max((1ll*a+ans)%A,(1ll*b+ans)%A);
    		l=lower_bound(h+1,h+1+n,l)-h;
    		r=upper_bound(h+1,h+1+n,r)-h-1;
    		printf("%lld
    ",ans=query(x,l,r));
    	}
    }
  • 相关阅读:
    406. 根据身高重建队列 vector 自定义排序
    5552. 到家的最少跳跃次数 BFS
    5602. 将 x 减到 0 的最小操作数 双指针
    Hadoop Container is running beyond memory limits code143
    1122. 数组的相对排序 自定义排序
    328. 奇偶链表 链表
    127. 单词接龙 bfs
    5600. 第 K 条最小指令 组合数学
    5548. 最小体力消耗路径 二分
    1024. 视频拼接 dp
  • 原文地址:https://www.cnblogs.com/Khada-Jhin/p/9568574.html
Copyright © 2011-2022 走看看