2023NOIP A层联测14 vivo50(分块+树链剖分)

文章讲述了如何在一个有n个点和q次询问的树中,通过分块和树链剖分的方法,高效地计算出给定区间内两点之间的最小花费路径。主要涉及lca(最近公共祖先)的计算和在线段树上的优化查询。

题目大意

有一棵nnn个点的树,每条边都有边权。uuuvvv的花费为uuuvvv的路径上的边的边权和。

qqq次询问,每次询问给出p,q,vp,q,vp,q,v,要求在[p,q][p,q][p,q]中选择一个uuu,使得uuuvvv的花费最小,并输出这个最小值。

保证树上任意一点到另一点的花费不超过10910^9109

时间限制5000ms5000ms5000ms,空间限制1024MB1024MB1024MB

1≤n,q≤1051\leq n,q\leq 10^51n,q105


题解

我们考虑用分块来做。

对于散块,枚举块上的每个点iiiO(log⁡n)O(\log n)O(logn)iiivvvlcalcalca,用toi+tov−2tolcato_i+to_v-2to_{lca}toi+tov2tolca更新答案,其中toito_itoi表示从根节点到结点iii的花费,处理所有散块的总时间复杂度为O(qnlog⁡n)O(q\sqrt n\log n)O(qnlogn)

对于每个整块,我们先预处理好块内的信息,再用这个块的信息更新每个询问。

对于一个点iii,如果它是uuuvvvlcalcalca,则uuuvvv的花费为tou+tov−2toito_u+to_v-2to_itou+tov2toi。当vvv确定时,我们要求一个uuu,满足u∈[p,q]u\in[p,q]u[p,q]tou−2toito_u-2to_itou2toi最小。

那么,假设当前枚举到第kkk个块,我们对图做一次dfsdfsdfsmnimn_imnimnimn_imni表示点iii的子树中所有在第kkk块中的最小的touto_utou。那么,我们枚举vvvvvv的每个祖先,假设枚举到xxx,则可以用mnx−2tox+tovmn_x-2to_x+to_vmnx2tox+tov来更新答案。

但是,万一xxx不是uuuvvvlcalcalca,统计的答案中不就包含了不合法的答案了吗?

uuuvvvlcalcalcayyy,当前枚举的节点为xxxxxxyyy的祖先,则路径u−x−vu-x-vuxv的花费一定比路径u−y−vu-y-vuyv的花费多,而答案要求的是最小值,所以路径u−x−vu-x-vuxv的花费会被路径u−y−vu-y-vuyv的花费所覆盖。也就是说,所有不合法的方案都能被合法的方案覆盖,这也就保证了这种方法的正确性。

那么,我们要求mnx−2tox+tovmn_x-2to_x+to_vmnx2tox+tov的最小值,也就是求从vvv到根节点上每个点xxxmnx−2toxmn_x-2to_xmnx2tox的最小值,这个用树链剖分即可解决。

然而,树链剖分的查询是O(log⁡2n)O(\log^2n)O(log2n)的,而总共会查询O(qn)O(q\sqrt n)O(qn)次,那么总时间复杂度为O(qnlog⁡2n)O(q\sqrt n\log^2n)O(qnlog2n),我们考虑优化。

我们先思考为什么每次查询是O(log⁡2n)O(\log^2n)O(log2n),因为在查询vvv时要向上跳log⁡n\log nlogn次,每次要在线段树上O(log⁡n)O(\log n)O(logn)查询。那么,我们可以用一个东西存储每个点向上跳一次时在线段树上查询的结果,下一次再遇到这个点时就可以O(1)O(1)O(1)查询。因为总共有nnn个点,每个点最多会在线段树中查询一次,那么qqq次查询的时间复杂度平摊下来就是O(nlog⁡n)O(n\log n)O(nlogn)的。于是,我们将qqq次查询的时间复杂度从O(qlog⁡2n)O(q\log^2n)O(qlog2n)优化成O(nlog⁡n)O(n\log n)O(nlogn)。那么,处理n\sqrt nn个块的总时间复杂度为O(nnlog⁡n)O(n\sqrt n\log n)O(nnlogn)

总时间复杂度为O((n+q)nlog⁡n)O((n+q)\sqrt n\log n)O((n+q)nlogn),空间复杂度为O(nn)O(n\sqrt n)O(nn),因为时间限制和空间限制都比较大,所以是可以过的。

一些缩短运行时间的小技巧

如果你TLETLETLE了,可以采用以下方法来缩短运行时间。

  • 因为在分块中最前面部分和最后面部分不管是整块还是散块,都是按散块来处理,所以处理整块的时候不需要处理第一块和最后一块
  • vectorvectorvector来维护每个块需要更新的询问,在每个询问求整块时放进对应块的vectorvectorvector,这样对于每个块就不用再将每个询问都枚举一遍了,这也是空间复杂度达到O(nn)O(n\sqrt n)O(nn)的原因
  • 因为输入量比较大,你可以使用快读

当然,如果你实现得比较好,常数比较小的话,不用这些方法也是可以过的。

code

#include<bits/stdc++.h>
#define lc k<<1
#define rc k<<1|1
#define rg register
using namespace std;
const int N=100000,B=320;
int n,q,bl,tot=0,d[2*N+5],l[2*N+5],r[2*N+5],w[2*N+5];
int dep[N+5],fa[N+5],siz[N+5],son[N+5],s[N+5],re[N+5],tp[N+5];
long long to[N+5],mn[N+5],mw[N+5],ans[N+5],tr[4*N+5];
struct node{
	int v,id;
};
vector<node>hv[B+5];
int rd(){
	int t=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){
		t=t*10+ch-'0';
		ch=getchar();
	}
	return t;
}
int pos(int i){return (i-1)/bl+1;}
void add(int xx,int yy,int zz){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;w[tot]=zz;
}
void dfs1(int u,int f){
	fa[u]=f;
	dep[u]=dep[f]+1;
	siz[u]=1;
	for(rg int i=r[u];i;i=l[i]){
		if(d[i]==f) continue;
		to[d[i]]=to[u]+w[i];
		dfs1(d[i],u);
		siz[u]+=siz[d[i]];
		if(siz[d[i]]>siz[son[u]]) son[u]=d[i];
	}
}
void dfs2(int u,int v){
	tp[u]=v;
	s[u]=++s[0];re[s[0]]=u;
	if(son[u]) dfs2(son[u],v);
	for(rg int i=r[u];i;i=l[i]){
		if(d[i]==fa[u]||d[i]==son[u]) continue;
		dfs2(d[i],d[i]);
	}
}
int lca(int x,int y){
	while(tp[x]!=tp[y]){
		if(dep[tp[x]]<dep[tp[y]]) swap(x,y);
		x=fa[tp[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}
void solve(int x,int y,int v,int id){
	int vl=pos(x),vr=pos(y);
	if(vl==vr){
		for(rg int i=x;i<=y;i++){
			ans[id]=min(ans[id],to[i]+to[v]-2*to[lca(i,v)]);
		}
		return;
	}
	for(rg int i=x;i<=vl*bl;i++){
		ans[id]=min(ans[id],to[i]+to[v]-2*to[lca(i,v)]);
	}
	for(rg int i=vr*bl-bl+1;i<=y;i++){
		ans[id]=min(ans[id],to[i]+to[v]-2*to[lca(i,v)]);
	}
	for(rg int i=vl+1;i<=vr-1;i++){
		hv[i].push_back((node){v,id});
	}
}
void dfs3(int u){
	for(rg int i=r[u];i;i=l[i]){
		if(d[i]==fa[u]) continue;
		dfs3(d[i]);
		mn[u]=min(mn[u],mn[d[i]]);
	}
}
void build(int k,int l,int r){
	if(l==r){
		tr[k]=mn[re[l]]-2*to[re[l]];
		return;
	}
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	tr[k]=min(tr[lc],tr[rc]);
}
long long find(int k,int l,int r,int x,int y){
	if(l>=x&&r<=y) return tr[k];
	int mid=l+r>>1;
	long long re=1e15;
	if(x<=mid) re=min(re,find(lc,l,mid,x,y));
	if(y>mid) re=min(re,find(rc,mid+1,r,x,y));
	return re;
}
long long gt(int x){
	long long re=1e15;
	while(tp[x]){
		if(mw[x]==1e15) mw[x]=find(1,1,n,s[tp[x]],s[x]);
		re=min(re,mw[x]);
		x=fa[tp[x]];
	}
	return re;
}
int main()
{
//	freopen("game.in","r",stdin);
//	freopen("game.out","w",stdout);
	n=rd();
	bl=sqrt(n);
	for(rg int i=1,x,y,z;i<n;i++){
		x=rd();y=rd();z=rd();
		add(x,y,z);add(y,x,z);
	}
	dfs1(1,0);
	dfs2(1,1);
	q=rd();
	for(rg int i=1,x,y,v;i<=q;i++){
		x=rd();y=rd();v=rd();
		ans[i]=1e15;
		solve(x,y,v,i);
	}
	for(rg int i=2;i<pos(n);i++){
		for(rg int j=1;j<=n;j++) mn[j]=mw[j]=1e15;
		for(rg int j=i*bl-bl+1;j<=i*bl;j++) mn[j]=to[j];
		dfs3(1);
		build(1,1,n);
		for(rg int j=0;j<hv[i].size();j++){
			ans[hv[i][j].id]=min(ans[hv[i][j].id],gt(hv[i][j].v)+to[hv[i][j].v]);
		}
	}
	for(rg int i=1;i<=q;i++){
		printf("%lld\n",ans[i]);
	}
	return 0;
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值