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

题目大意

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

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

保证树上任意一点到另一点的花费不超过 1 0 9 10^9 109

时间限制 5000 m s 5000ms 5000ms,空间限制 1024 M B 1024MB 1024MB

1 ≤ n , q ≤ 1 0 5 1\leq n,q\leq 10^5 1n,q105


题解

我们考虑用分块来做。

对于散块,枚举块上的每个点 i i i O ( log ⁡ n ) O(\log n) O(logn) i i i v v v l c a lca lca,用 t o i + t o v − 2 t o l c a to_i+to_v-2to_{lca} toi+tov2tolca更新答案,其中 t o i to_i toi表示从根节点到结点 i i i的花费,处理所有散块的总时间复杂度为 O ( q n log ⁡ n ) O(q\sqrt n\log n) O(qn logn)

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

对于一个点 i i i,如果它是 u u u v v v l c a lca lca,则 u u u v v v的花费为 t o u + t o v − 2 t o i to_u+to_v-2to_i tou+tov2toi。当 v v v确定时,我们要求一个 u u u,满足 u ∈ [ p , q ] u\in[p,q] u[p,q] t o u − 2 t o i to_u-2to_i tou2toi最小。

那么,假设当前枚举到第 k k k个块,我们对图做一次 d f s dfs dfs m n i mn_i mni m n i mn_i mni表示点 i i i的子树中所有在第 k k k块中的最小的 t o u to_u tou。那么,我们枚举 v v v v v v的每个祖先,假设枚举到 x x x,则可以用 m n x − 2 t o x + t o v mn_x-2to_x+to_v mnx2tox+tov来更新答案。

但是,万一 x x x不是 u u u v v v l c a lca lca,统计的答案中不就包含了不合法的答案了吗?

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

那么,我们要求 m n x − 2 t o x + t o v mn_x-2to_x+to_v mnx2tox+tov的最小值,也就是求从 v v v到根节点上每个点 x x x m n x − 2 t o x mn_x-2to_x mnx2tox的最小值,这个用树链剖分即可解决。

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

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

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

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

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

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

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

code

#include
#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;
}

你可能感兴趣的:(题解,好题,题解,c++)