NOIP2023模拟7联测28 距离

题目大意

给一棵 n n n个节点的无根树,每条边都有一个边权 w i w_i wi。令 d i s ( x , y ) dis(x,y) dis(x,y)为点 x x x到点 y y y的距离。

你需要维护一个初始为空的点对集合 S S S,有 m m m次操作,每次操作有两种类型:

  • 1 a b S S S中插入点对 ( a , b ) (a,b) (a,b)
  • 2 x y查询下面的式子并输出结果
    min ⁡ ( a , b ) ∈ S d i s ( x , a ) + d i s ( y , b ) \min\limits_{(a,b)\in S}dis(x,a)+dis(y,b) (a,b)Smindis(x,a)+dis(y,b)

若查询时 S S S为空,则输出 − 1 -1 1

1 ≤ n , m ≤ 2 × 1 0 5 , 1 ≤ w i ≤ 1 0 9 1\leq n,m\leq 2\times 10^5,1\leq w_i\leq 10^9 1n,m2×105,1wi109

在子任务 2 2 2中, b = 1 b=1 b=1

时间限制 4000 m s 4000ms 4000ms,空间限制 512 M B 512MB 512MB


题解

首先,我们考虑子任务 2 2 2,此时相当于点对变成了但单个点。问题就变成了每次往点集插入一个点,或者询问距离某个点最近的点的距离。

考虑用点分树,在每个重心维护其点分树内距离重心最近的点即可,修改和查询时不断往上跳即可。时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

下面考虑如何处理点对的情况。

我们可以将所有操作都离线下来,将 a i a_i ai x i x_i xi进行点分治,在每一层点分治中处理所有 a i a_i ai x i x_i xi在其点分治子树中的操作。设当前的分治中心为点 u u u,则按时间顺序在另一棵点分树上插入 b i b_i bi,并加上 d i s ( u , a i ) dis(u,a_i) dis(u,ai),查询时就使用使用普通点分树的操作查询即可(也就是不断往上跳)。

每次修改和查询会在点分治中的 O ( log ⁡ n ) O(\log n) O(logn)个点中体现,每次体现都要在点分树上 O ( log ⁡ n ) O(\log n) O(logn)来进行修改和查询,所以总时间复杂度为 O ( m log ⁡ 2 n ) O(m\log^2n) O(mlog2n)

可以参考代码帮助理解。

code

#include
using namespace std;
const int N=200000;
int n,m,fl=0,root,rt=0,tot=0,d[2*N+5],l[2*N+5],r[N+5];
int siz[N+5],mxd[N+5],z[N+5],fa[N+5],dw[N+5];
long long w[2*N+5],mn[N+5],ans[N+5],dis[N+5][20];
struct que{
	int op,x,y;
}q[N+5];
vector<int>G[N+5],id[N+5];
void add(int xx,int yy,int zz){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;w[tot]=zz;
}
void dfs(int u,int fa,int all,int dw){
	siz[u]=1;mxd[u]=0;mn[u]=1e18;
	for(int i=r[u];i;i=l[i]){
		if(d[i]==fa||z[d[i]]) continue;
		dis[d[i]][dw]=dis[u][dw]+w[i];
		dfs(d[i],u,all,dw);
		siz[u]+=siz[d[i]];
		mxd[u]=max(mxd[u],siz[d[i]]);
	}
	mxd[u]=max(mxd[u],all-siz[u]);
	if(mxd[u]<mxd[rt]) rt=u;
}
void dd(int u){
	z[u]=1;
	for(int i=r[u];i;i=l[i]){
		if(z[d[i]]) continue;
		rt=0;
		dis[d[i]][dw[u]]=w[i];
		dfs(d[i],0,siz[d[i]],dw[u]);
		G[u].push_back(rt);fa[rt]=u;
		dw[rt]=dw[u]+1;
		dd(rt);
	}
}
void pt(int x,long long v){
	for(int tx=x;tx;tx=fa[tx]){
		mn[tx]=min(mn[tx],v+dis[x][dw[tx]]);
	}
}
long long gt(int x){
	long long re=1e18;
	for(int tx=x;tx;tx=fa[tx]){
		re=min(re,mn[tx]+dis[x][dw[tx]]);
	}
	return re;
}
void clr(int x){
	for(int tx=x;tx;tx=fa[tx]) mn[tx]=1e18;
}
void solve(int u){
	for(int i:id[u]){
		if(q[i].op==1) pt(q[i].y,dis[q[i].x][dw[u]]);
		else ans[i]=min(ans[i],gt(q[i].y)+dis[q[i].x][dw[u]]);
	}
	for(int i:id[u]){
		if(q[i].op==1) clr(q[i].y);
	}
	for(int v:G[u]) solve(v);
}
int main()
{
	freopen("distance.in","r",stdin);
	freopen("distance.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1,x,y,z;i<n;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&q[i].op,&q[i].x,&q[i].y);
		ans[i]=1e18;
	}
	mxd[0]=1e9;dfs(1,0,n,0);
	root=rt;dw[root]=1;dd(root);
	for(int i=1;i<=m;i++){
		for(int u=q[i].x;u;u=fa[u]) id[u].push_back(i);
	}
	solve(root);
	for(int i=1;i<=m;i++){
		if(q[i].op==1) fl=1;
		else{
			if(!fl) printf("-1\n");
			else printf("%lld\n",ans[i]);
		}
	}
	return 0;
}

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