zoukankan      html  css  js  c++  java
  • BZOJ4012 [HNOI2015]开店

    BZOJ4012 [HNOI2015]开店

    这道题因为太多人拿这个题卡$BZOJ$,于是成了权限题。。。

    本蒟蒻表示没钱氪金。。。

    无奈,拿出了洛谷:P3241 [HNOI2015]开店

    还有$LOJ$:#2116. 「HNOI2015」开店

    这里附上洛谷的题面:

    题目描述

    风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。

    这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有$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$ ,表示树的大小、开店的方案个数和妖怪的年龄上限。

    第二行nn 个用空格分开的数$x_1,x_2,ldots,x_n;x_i$表示第$i$个地点妖怪的年龄,满足$0le x_ilt A$。(年龄是可以为$0$的,例如刚出生的妖怪的年龄为$0$ 。)

    接下来$n-1$ 行,每行三个用空格分开的数$a$ 、$b$ 、$c$ ,表示树上的顶点$a$ 和$b$ 之间有一条权为$c(1le cle1000)$的边,$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)$ 。

    输出格式:

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

    输入输出样例

    输入样例#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

    说明

    满足$nle1.5 imes 10^5,Qle2 imes 10^5$ 。对于所有数据,满足$A<=10^9$


    题解Here!

    感觉做完这题之后,自己的码力和几个月前不一样了。。。

    据说这题是动态淀粉质点分治?然而本蒟蒻并不会。。。

    有时间再填坑吧感觉这辈子是不可能填坑的。。。

    于是拿出了树链剖分+主席树。

    每次询问点权在$[L,R]$之间的所有点到某个点的距离之和,并且强制在线。

    首先考虑一个简化的版本:

    询问所有点到点$u$的距离和。

    然后开始漫长地推式子。。。

    当然,可以跳过漫长的过程直接看结论。。。

    设$deep[i],size[i]$分别表示以$1$为根时第$i$个点的带权深度和子树大小。

    在我的代码中为了避免变量名冲突,就用$dis[i]$代替了这里的$deep[i]$,这点要注意一下。

    观察$1$为根和$u$为根会发生哪些变化。

    $u$的子树中某节点$v$的深度会从$deep[v]$变成$deep[v]-deep[u]$,相当于都减少了$deep[u]$,且有$size[u]$个点发生了此变化。

    $fa[u]$的子树,且不是$u$的子树中的某节点$v$,深度会从$deep[v]$变成$deep[v]-deep[fa[u]]+deep[u]-deep[fa[u]]$,相当于减少了$2 imes deep[fa[u]]-deep[u]$,且有$size[fa[u]]-size[u]$个点发生了此变化。

    之后以此类推。

    更具体的描述,定义$a_i$为$1$至$u$的链上的第$i$个点,$1$至$u$的链上共有$k$个点,那么所有点到$u$的距离之和可以用如下式子表示:

    $$sum_{i=1}^ndeep[i]-sum_{i=1}^{k-1}(size[a_i]-size[a_i+1]) imes (2 imes deep[a_i]-deep[u])-size[u] imes deep[u]$$

    展开:

    $$sum_{i=1}^ndeep[i]-size[u] imes deep[u]-(2 imes sum_{i=1}^{k-1}size[a_i] imes deep[a_i]-2 imessum_{i=1}^{k-1}size[a_{i+1}] imes deep[a_i]-sum_{i=1}^{k-1}size[a_i] imes deep[u]+sum_{i=1}^{k-1}size[a_{i+1}] imes deep[u])$$

    $$=sum_{i=1}^ndeep[i]-size[u] imes deep[u]-2 imessum_{i=1}^{k-1}size[a_i] imes deep[a_i]+2sum_{i=1}^{k-1}size[a_{i+1}] imes deep[a_i]+sum_{i=1}^{k-1}size[a_i] imes deep[u]-sum_{i=1}^{k-1}size[a_{i+1}] imes deep[u]$$

    把其中的某个式子提出来,化简:

    $$sum_{i=1}^{k-1}size[a_i] imes deep[u]-sum_{i=1}^{k-1}size[a_{i+1}] imes deep[u]$$

    $$=sum_{i=1}^{k-1}size[a_i] imes deep[u]-sum_{i=2}^ksize[a_i] imes deep[u]$$

    $$=size[a_1] imes deep[u]-size[a_k] imes deep[u]$$

    $$=n imes deep[u]-size[u] imes deep[u]$$

    于是原式变为:

    $$sum_{i=1}^ndeep[i]+n imes deep[u]-2 imes size[u] imes deep[u]-2 imes sum_{i=1}^{k-1}size[a_i] imes deep[a_i]+2 imes sum_{i=1}^{k-1}size[a_{i+1}] imes deep[a_i]$$

    现在观察:

    $$-2sum_{i=1}^{k-1}size[a_i] imes deep[a_i]+2sum_{i=1}^{k-1}size[a_{i+1}] imes deep[a_i]$$

    即:

    $$2sum_{i=1}^{k-1}size[a_{i+1}] imes deep[a_i]-2sum_{i=1}^{k-1}size[a_i] imes deep[a_i]$$

    这里直接相减出现的$size[a_i]-size[a_{i+1}]$难以处理,我们考虑进行错位相减。

    $$ ext{原式}=2sum_{i=2}^ksize[a_i] imes deep[a_{i-1}]-2sum_{i=1}^{k-1}size[a_i] imes deep[a_i]$$

    $$=2sum_{i=2}^ksize[a_i] imes deep[a_{i-1}]-2sum_{i=2}^{k-1}size[a_i] imes deep[a_i](deep[a_1]=deep[1]=0)$$

    $$=2 imes size[a_k] imes deep[a_{k-1}]+2sum_{i=2}^{k-1}size[a_i] imes (deep[a_{i-1}]-deep[a_i])$$

    将原式中的$-2 imes size[u] imes deep[u]$并入上式中,得到:

    $$2 imes size[a_k] imes deep[a_{k-1}]-2 imes size[a_k] imes deep[a_k]+2sum_{i=2}^{k-1}size[a_i] imes (deep[a_{i-1}]-deep[a_i])$$

    $$=2sum_{i=2}^ksize[a_i](deep[a_{i-1}]-deep[a_i])$$

    注意到$deep[a_i]-deep[a_{i-1}]$是点$i$到其父节点的边权,设为$fv[i]$。

    故原式等于:

    $$sum_{i=1}^ndeep[i]+n imes deep[u]-2sum_{i=2}^ksize[a_i] imes fv[a_i]$$

    到此,我们的推式子结束了。

    并且这玩意可以进行维护了。

    现在考虑如何加入$[L,R]$的限制。

    直接通过子树查询的方式进行,单次复杂度与树高约为线性关系,显然是铁定$TLE$的。

    怎么办?

    这时便要体会主席树的版本作用。

    将点按照点权排序,一个一个加入,最终答案便是$R$对应版本的主席树的答案减去$L-1$对应版本的主席树的答案。

    也就是做一个差分:$Ans(L,R)=Ans(1,R)-Ans(1,L-1)$

    而且,每次加入一个点的时候,树的形态不发生变化,$fv[i]$不发生变化,只有$size[i]$发生变化。

    所以只需把加入的这个点到根的路径上的所有点的$size$进行$+1$即可。

    查询时从当前指定的点出发,向上统计$sum size[i] imes fv[i]$。

    这是可以通过树链剖分维护的。

    由于空间限制,需要使用标记永久化。

    这题目就做完了。

    好长。。。

    附代码:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #define MAXN 200010
    using namespace std;
    int n,m,age,c=1,d=1;
    int root[MAXN];
    int head[MAXN],deep[MAXN],son[MAXN],size[MAXN],fa[MAXN],id[MAXN],pos[MAXN],top[MAXN];
    long long dis[MAXN],sum[MAXN];
    struct Tree{
    	int next,to,w;
    }a[MAXN<<1];
    struct Point{
    	int val,id;
    	friend bool operator <(const Point &p,const Point &q){
    		if(p.val==q.val)return p.id<q.id;
    		return p.val<q.val;
    	}
    }point[MAXN];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    namespace CT{
    	#define LSON(x) a[x].l
    	#define RSON(x) a[x].r
    	#define DATA(x) a[x].data
    	#define SUM(x) a[x].sum
    	#define SIGN(x) a[x].c
    	int size=0;
    	struct Chairman_Tree{
    		long long data,sum;
    		int l,r,c;
    	}a[MAXN<<7];
    	inline void pushup(int rt){
    		DATA(rt)=DATA(LSON(rt))+DATA(RSON(rt));
    		SUM(rt)=SUM(LSON(rt))+SUM(RSON(rt))+DATA(rt)*SIGN(rt);
    	}
    	inline void buildtree(int l,int r,int &rt){
    		a[++size]=a[rt];rt=size;SIGN(rt)=LSON(rt)=RSON(rt)=SUM(rt)=DATA(rt)=0;
    		if(l==r){
    			DATA(rt)=dis[pos[l]]-dis[fa[pos[l]]];
    			return;
    		}
    		int mid=l+r>>1;
    		buildtree(l,mid,LSON(rt));
    		buildtree(mid+1,r,RSON(rt));
    		pushup(rt);
    	}
    	void update(int l,int r,int lside,int rside,int &rt){
    		a[++size]=a[rt];rt=size;
    		if(l<=lside&&rside<=r){
    			SIGN(rt)++;
    			SUM(rt)+=DATA(rt);
    			return;
    		}
    		int mid=lside+rside>>1;
    		if(l<=mid)update(l,r,lside,mid,LSON(rt));
    		if(mid<r)update(l,r,mid+1,rside,RSON(rt));
    		pushup(rt);
    	}
    	long long query(int l,int r,int c,int lside,int rside,int rt){
    		long long ans=0;
    		if(l<=lside&&rside<=r)return (SUM(rt)+DATA(rt)*c);
    		c+=SIGN(rt);
    		int mid=lside+rside>>1;
    		if(l<=mid)ans+=query(l,r,c,lside,mid,LSON(rt));
    		if(mid<r)ans+=query(l,r,c,mid+1,rside,RSON(rt));
    		return ans;
    	}
    }
    inline void add(int u,int v,int w){
    	a[c].to=v;a[c].w=w;a[c].next=head[u];head[u]=c++;
    	a[c].to=u;a[c].w=w;a[c].next=head[v];head[v]=c++;
    }
    void dfs1(int rt){
    	son[rt]=0;size[rt]=1;
    	for(int i=head[rt];i;i=a[i].next){
    		int will=a[i].to;
    		if(!deep[will]){
    			deep[will]=deep[rt]+1;
    			dis[will]=dis[rt]+a[i].w;
    			fa[will]=rt;
    			dfs1(will);
    			size[rt]+=size[will];
    			if(size[son[rt]]<size[will])son[rt]=will;
    		}
    	}
    }
    void dfs2(int rt,int f){
    	id[rt]=d++;pos[id[rt]]=rt;top[rt]=f;
    	if(son[rt])dfs2(son[rt],f);
    	for(int i=head[rt];i;i=a[i].next){
    		int will=a[i].to;
    		if(will!=fa[rt]&&will!=son[rt])dfs2(will,will);
    	}
    }
    void update_path(int x,int y,int u){
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		CT::update(id[top[x]],id[x],1,n,root[u]);
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	CT::update(id[x],id[y],1,n,root[u]);
    }
    long long query_path(int x,int y,int u){
    	long long s=0;
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		s+=CT::query(id[top[x]],id[x],0,1,n,root[u]);
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	s+=CT::query(id[x],id[y],0,1,n,root[u]);
    	return s;
    }
    long long solve(int x,int u){
    	return (sum[u]+dis[x]*u-2LL*query_path(1,x,u));
    }
    void work(){
    	int l,r,u;
    	long long last=0;
    	while(m--){
    		u=read();l=read();r=read();
    		l=(l+last)%age;r=(r+last)%age;
    		if(l>r)swap(l,r);
    		l=lower_bound(point+1,point+n+1,(Point){l,0})-point;
    		r=upper_bound(point+1,point+n+1,(Point){r,MAXN})-point-1;
    		last=solve(u,r)-solve(u,l-1);
    		printf("%lld
    ",last);
    	}
    }
    void init(){
    	int u,v,w;
    	n=read();m=read();age=read();
    	for(int i=1;i<=n;i++){
    		point[i].val=read();
    		point[i].id=i;
    	}
    	for(int i=1;i<n;i++){
    		u=read();v=read();w=read();
    		add(u,v,w);
    	}
    	deep[1]=1;
    	dfs1(1);
    	dfs2(1,1);
    	sort(point+1,point+n+1);
    	CT::buildtree(1,n,root[0]);
    	for(int i=1;i<=n;i++){
    		sum[i]=sum[i-1]+dis[point[i].id];
    		root[i]=root[i-1];
    		update_path(1,point[i].id,i);
    	}
    }
    int main(){
    	init();
    	work();
        return 0;
    }
    

  • 相关阅读:
    【WPF】给下拉列表ComboBox绑定数据
    【C#】POST请求参数含中文,服务器解析得到乱码
    CentOS下搭建SVN服务器
    MySQL之ALTER
    深入PHP内核之ZVAL
    关于zend_parse_parameters函数
    PHP数组
    shell中比较字符串大小,>和<前需要加上进行转义,否则会输出到文件了
    awk编程基础
    【读书笔记】《Python_Cookbook3》第一章:数据结构和算法
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9601611.html
Copyright © 2011-2022 走看看