zoukankan      html  css  js  c++  java
  • [HNOI2015]开店-题解

    题目【IN

    目前只有开O2能过,STL的常数太大了,QWQ


    题意简述,给你一棵树,有点权与边权,然后有很多询问,每次询问你点权在LRLsim R范围内的点到给点点vv的距离之和。


    解法1:动态点分治

    我们使用动态点分治来求取答案。

    先构建出点分树,树高肯定是在lognlogn级别内的,然后对于一个分治点(重心),我们在上面记录三个值:

    • s1s1表示该重心的子树点数
    • s2s2表示该重心子树中的点到重心的距离和
    • s3s3表示该重心的子树中的点到重心在点分树上的父亲的距离和

    我们维护这三个值是因为计算答案时,对于子树内的点我们可以直接加上,对于祖先部分的点我们直接加上直接到祖先的距离,而对于其它部分的点,我们将其拆分为ulcavu ightarrow lca ightarrow v两条路来统计,然后再减去多余的。

    那么对应的式子为
    s2[u]+(s2[f[p]]s3[p])+(s1[f[p]]s1[p])×dist(f[p],u)s2[u]+sum (s2[f[p]]-s3[p])+(s1[f[p]]-s1[p]) imes dist(f[p],u)
    其中pp开始=u=u,然后不断往点分树的根跳即可。
    但是在实际的统计中,我们这样实现:

    • 先加上子树到当前枚举的pp的距离和。
    • 然后如果当前的点p!=up!=u,那么我们再加上当前点pp到点uu的距离乘以子树大小。
    • 如果点pp还有分治树上的父亲,那么我们还要减去当前点pp子树到其父亲距离和,还有点pp父亲f[p]f[p]到点uu的距离乘以点pp的子树大小。

    下面上一张图来详细的讲解一下为什么如此计算:
    在这里插入图片描述

    我们发现对于当前要求的点uu,如果我们p=up=u,那么就直接加上当前子树到pp的距离和,由于还有父亲,所以我们看,当pp为父亲vv时,我们这样操作:

    • 先将pp所有子树内的点走到pp,代价为s2s2
    • 然后我们减去原来的uu的子树内的点多走的距离,就是它们先走到了pp,又从pp走回了uu,所以减去它们到pp的距离和,还有pup ightarrow u这条边被它们多走的距离。

    那么操作的正确性就非常显然了。

    而对于权值在LRLsim R的限制,我们可以利用前缀和的思想,求1L11sim L-11R1sim R的答案相减即可得到,所以用一个setset或者vectorvector存下来即可(C++中的STLSTL

    那么每次询问时即可在点分树上跳父亲统计答案即可,复杂度为log2nlog^2n,预处理点分树nlog2nnlog^2n,预处理LCA m LCA我们用Rmq m Rmq来,所以为nlognnlogn,查询LCA m LCA的复杂度变成O(1)O(1),所以总的复杂度为nlogn+nlog2n+mlog2nnlogn+nlog^2n+mlog^2n,对于2e52e5的数据再加上大常数,所以要开O2O2优化。

    代码丑陋QAQ

    #include<vector>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    #define ll long long
    using namespace std;
    const int M=5e5+10;
    int n,Q,A;
    int age[M];
    struct ss{
    	int to,last;ll len;
    	ss(){}
    	ss(int a,int b,ll c):to(a),last(b),len(c){}
    }g[M<<1];
    int head[M],cnt;
    void add(int a,int b,ll c){
    	g[++cnt]=ss(b,head[a],c);head[a]=cnt;
    	g[++cnt]=ss(a,head[b],c);head[b]=cnt;
    }
    
    struct node{
    	int p;
    	ll s1,s2,s3;
    	node(){}
    	node(int a,ll b,ll c,ll d):p(a),s1(b),s2(c),s3(d){}
    	bool operator <(const node &a)const{return p<a.p;}
    };
    vector <node> rec[M];
    #define pb push_back
    typedef vector<node>::iterator iter;
    
    int pos[M],tim,lgp[M],lg;
    ll lcaq[25][M],dis[M];
    //rmq求lca来求距离 
    void dfs_rmq(int a,int b){
    	lcaq[0][pos[a]=++tim]=dis[a];
    	for(int i=head[a];i;i=g[i].last){
    		if(g[i].to==b) continue;
    		dis[g[i].to]=dis[a]+g[i].len;
    		dfs_rmq(g[i].to,a);
    		lcaq[0][++tim]=dis[a];
    	}
    }
    void init_rmq(){
    	lgp[2]=lgp[3]=1;
    	for(int i=4;i<=tim;i++)lgp[i]=lgp[i>>1]+1;
    	for(lg=1;(1ll<<lg)<=tim;++lg);
    	for(int i=1;i<=lg;i++){
    		for(int j=1;j+(1<<(i-1))<=tim;j++){
    			lcaq[i][j]=min(lcaq[i-1][j],lcaq[i-1][j+(1<<(i-1))]);
    		}
    	}
    }
    ll dist(int a,int b){
    	ll now=dis[a]+dis[b];
    	a=pos[a];b=pos[b];
    	if(!a||!b) return 0;
    	if(a>b)swap(a,b);
    	int k=lgp[b-a+1];
    	ll lcaa=min(lcaq[k][a],lcaq[k][b-(1<<k)+1]);
    	return now-(lcaa<<1);
    }
    int sze[M],f[M],son[M],totsze,root;
    bool vis[M];
    //找重心 
    void findroot(int a,int fa){
    	sze[a]=1;son[a]=0;
    	for(int i=head[a];i;i=g[i].last){
    		int v=g[i].to;
    		if(v==fa||vis[v]) continue;
    		findroot(v,a);
    		sze[a]+=sze[v];
    		if(sze[v]>son[a])son[a]=sze[v];
    	}
    	if(totsze-sze[a]>son[a])son[a]=totsze-sze[a];
    	if(son[a]<son[root])root=a;
    }
    
    void getall(int a,int fa,int o){
    	//求信息 
    	rec[o].pb(node(age[a],1,dist(a,o),dist(a,f[o])));
    	for(int i=head[a];i;i=g[i].last){
    		if(g[i].to==fa||vis[g[i].to]) continue;
    		getall(g[i].to,a,o);
    	}
    }
    
    void find(int a){
    	//找点分树,并求取信息 
    	vis[a]=1;
    	getall(a,0,a);
    	rec[a].pb(node(-1,0,0,0));
    	sort(rec[a].begin(),rec[a].end());
    	for(int i=0,sz=rec[a].size()-1;i<sz;i++){
    		rec[a][i+1].s1+=rec[a][i].s1;
    		rec[a][i+1].s2+=rec[a][i].s2;
    		rec[a][i+1].s3+=rec[a][i].s3;
    	}//前缀和 
    	for(int i=head[a];i;i=g[i].last){
    		if(vis[g[i].to]) continue;
    		root=0;totsze=sze[g[i].to];
    		findroot(g[i].to,0);
    		f[root]=a;
    		find(root);
    	}
    }
    
    node calc(int o,int l,int r){
    	//以前缀和的形式保存:s1子树大小,s2子树到根,s3子树到根的分治父亲 
    	if(!o) return node(0,0,0,0);
    	iter a=upper_bound(rec[o].begin(),rec[o].end(),node(r,0,0,0));--a;
    	iter b=upper_bound(rec[o].begin(),rec[o].end(),node(l-1,0,0,0));--b;
    	ll s1=(a->s1)-(b->s1),s2=(a->s2)-(b->s2),s3=(a->s3)-(b->s3);
    	return node(0,s1,s2,s3);
    }
    
    ll getans(int o,int l,int r){
    	ll ans=0;
    	for(int a=o;a;a=f[a]){
    		node now=calc(a,l,r);
    		ans+=now.s2;//首先由子树到当前的根的贡献 
    		if(a!=o) ans+=now.s1*dist(a,o);//当前的点不是最开始询问点则需要加上子树到当前点a的多余贡献 
    		if(f[a]) ans-=now.s3+now.s1*dist(f[a],o);//如果有分治父亲,那么需要减去子树到根父亲和根到父亲的贡献。 
    		//因为到当前这个点的距离可以分成三种,一种是子树内的直接到,一种是祖先部分的也是直接到,一种为另外一子树内的点
    		//需要分成两部分,u->lca->v,所以这样就能统计出所有的答案。 
    	}
    	return ans;
    }
    ll zans;
    ll a,b,c;
    int main(){
    	scanf("%d%d%d",&n,&Q,&A);
    	for(int i=1;i<=n;i++)scanf("%d",&age[i]);
    	for(int i=1;i<n;i++){
    		scanf("%lld%lld%lld",&a,&b,&c);
    		add(a,b,c);
    	}
    	dfs_rmq(1,0);
    	init_rmq();
    	root=0;son[0]=M;totsze=n;
    	findroot(1,0);
    	find(root);
    	while(Q--){
    		scanf("%lld%lld%lld",&a,&b,&c);
    		b=(1ll*b+zans)%A;c=(1ll*c+zans)%A;
    		if(b>c)swap(b,c);
    		zans=getans(a,b,c);
    		cout<<zans<<'
    ';
    	}
    	return 0;
    } 
    
    • 动态点分治类似题目【IN-Luogu

    解法2:主席树+树剖

    我们可以发现,一个询问点的答案为如下式子:
    (i=1ndis(i))+n×dis(u)2×i=1ndis(lca(i,u))left(sumlimits_{i=1}^ndis(i) ight)+n imes dis(u)-2 imessumlimits_{i=1}^ndis(lca(i,u))

    其中dis(i)dis(i)表示ii到根节点的距离。

    这个式子和动态点分治中维护的信息是十分类似的,我们开始先加上所有点到根的距离,然后对于一部分点vv,它必须走vlcauv ightarrow lca ightarrow u这条路,所以还有根节点回来的路径为n×dis(u)n imes dis(u),但是这样无疑会多算一些边,所以我们利用差分的思想,一条路影响是不会超过lcalca的上方,所以我们减去lcalca到根的距离,由于一条路是两个点,所以减两次,那么便是答案了。

    对于信息的维护,我们用线段树+树链剖分即可,但是有点权LRLsim R的限制,所以我们用主席树即可做到维护,log2nlog^2n的查询。

    但是此题空间限制较小(对于主席树来说),所以我们使用标记永久化,减少新的节点的开销。

    下面上代码的连接我没有打主席树的代码,所以借用的他人的
    IN-Luogu

    复杂度O(n+nlog2n+mlog2n)O(n+nlog^2n+mlog^2n),常数较为小一点(因为没有使用过多的STLSTL)。

  • 相关阅读:
    MySQL 数据库常用命令
    HTML常用标签介绍
    浏览器 返回状态码汇总
    Mysql常用的三种数据库引擎比较
    系统常用端口大全
    nginx入门与实战
    Linux系统基础优化及常用命令
    python开发之virtualenv与virtualenvwrapper讲解
    常用服务安装部署
    远程连接Linux
  • 原文地址:https://www.cnblogs.com/VictoryCzt/p/10053405.html
Copyright © 2011-2022 走看看