Codeforces 983 E . NN country

本文介绍了一种解决在树形城市网络中寻找两人间最短班车路线的问题算法。通过预处理和倍增技巧,该算法能高效计算从任意两点出发到达最近公共祖先所需的最少班车数量。此外,还详细阐述了如何判断是否存在一条班车路线覆盖特定节点,进一步优化解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NN country

Description

nnn个城市形成一棵树的形状。
mmm辆双向班车往返于两个城市(中途经过的城市都会停)。
qqq个人要从城市xxx到城市yyy,问最少坐几趟班车。
如果到不了,输出−1-11

Data Constrints

1≤n,m,q≤2∗1051\leq n,m,q \leq 2*10^51n,m,q2105

Solution

首先从城市xxx到城市yyy的搭车方案分成两类,第一类为在xxxyyylcalcalca下车了,第二类是没有在lcalcalca处下车。
第一类比较好处理,预处理出gx,yg_{x,y}gx,y表示从xxx向上搭2y2^y2y趟车最上能去到哪个点,用ggg数组可以很快的倍增出xxxyyylcalcalca至少要搭多少辆车。

第二类的话先找到xxxyyyggg数组跳到lcalcalca处的前一个点,记为x′x'xy′y'y,就是找是否存在一趟车的路覆盖了x′x'xy′y'y,如果存在,那么答案显然可以少111

至于如何判断的话,实质就是问是否有一条路径一端在x′x'x的子树内,另一端在y′y'y的子树内,算出dfsdfsdfs序便变成了一个二维数点问题,但可以不用扫描线处理,具体做法是,考虑离线,dfsdfsdfs时每遍历到一个点时,找出所有以这个点为一端的车次,并给这些车次的另一端打上加1标记,那么对于一组询问x′x'xy′y'yy′y'y子树内的标记和在x′x'x的子树遍历完后发生了变化,就说明存在一条路径一端在x′x'x的子树内,另一端在y′y'y的子树内,由于子树内的dfsdfsdfs序是连续的一段,所以询问子树内的标记和用树状数组就可以解决,那这题就做完了。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=51e4,M=N<<2;

int ne[M],lb[M],la[N],dfn[N],en[N],dep[N],fa[N],ans[N];
int ax[N],ay[N];

int f[N][20],g[N][20];
int xx[N],yy[N];
int u1[N],u2[N],v1[N],v2[N],jl[N];

int n,m,x,y,k,op,fs,oo,l,q;
int tr[M];

inline void llb(int a,int b)
{ne[++oo]=la[a]; la[a]=oo; lb[oo]=b;}

inline int min(int a,int b)
{return a<b?a:b;}

inline void dg(int o)
{
	for(int y=la[o];y;y=ne[y]){
		f[lb[y]][0]=o; dep[lb[y]]=dep[o]+1;
		for(l=0;f[f[lb[y]][l]][l];++l)
		f[lb[y]][l+1]=f[f[lb[y]][l]][l];
		dg(lb[y]);
	}
}

inline int get(int a,int b)
{
	if(dep[a]>dep[b])swap(a,b);
	for(int l=19;l>=0;--l)if(dep[f[b][l]]>=dep[a])b=f[b][l];
	for(int l=19;l>=0;--l)if(f[b][l]!=f[a][l])b=f[b][l],a=f[a][l];
	return (a!=b)?fa[a]:a;
}

inline void dfs1(int o)
{
	for(int y=la[o];y;y=ne[y]){
		dfs1(lb[y]);
		if(dep[g[lb[y]][0]]<dep[g[o][0]])g[o][0]=g[lb[y]][0];
		if(dep[g[o][0]]>=dep[o])g[o][0]=n+1;
	}
}

inline void dfs(int o)
{
	dfn[o]=en[o]=++op;
	for(int y=la[o];y;y=ne[y]){
		k=lb[y];
		if(dep[g[k][0]]<dep[k]){
			for(l=0;g[g[k][l]][l]!=n+1;++l)
			g[k][l+1]=g[g[k][l]][l];
		}else g[k][0]=n+1;
		dfs(k); en[o]=en[k];
	}
}

inline int get1(int a,int b)
{
	if(a==b)return 0;
	int aa=0;
	for(int l=19;l>=0;--l)
	if(g[a][l]!=n+1&&dep[g[a][l]]>dep[b])
	a=g[a][l],aa=aa+(1<<l);
	if(g[a][0]!=n+1)aa=aa+1;else aa=n+1;
	return aa;
}

inline int get2(int a,int b)
{
	int aa=0;
	for(int l=19;l>=0;--l)
	if(g[a][l]!=n+1&&dep[g[a][l]]>dep[b])
	a=g[a][l],aa=aa+(1<<l);
	fs=aa; return a;
}

inline void modify(int o,int zl)
{for(;o<=n;o=o+(o&(-o)))tr[o]=tr[o]+zl;}

inline int ask(int o)
{
	int y=0;
	for(;o;o=o-(o&(-o)))y=y+tr[o];
	return y;
}

inline void dfs2(int o)
{
	for(int y=la[o];y;y=ne[y])
	if(lb[y]>2*n){
		k=lb[y]-2*n;
		jl[k]=ask(en[u2[k]])-ask(dfn[u2[k]]-1);
	}
	for(int y=la[o];y;y=ne[y])
	if(lb[y]>n&&lb[y]<=2*n){
		k=lb[y]-n;
		modify(dfn[k],1);
	}
	for(int y=la[o];y;y=ne[y])
	if(lb[y]<=n)dfs2(lb[y]);
	for(int y=la[o];y;y=ne[y])
	if(lb[y]>2*n){
		k=lb[y]-2*n;
		if(ask(en[u2[k]])-ask(dfn[u2[k]]-1)!=jl[k])
		ans[k]=min(ans[k],v1[k]+v2[k]+1);
	}
}

int main()
{
	cin>>n;
	fo(i,2,n){
		scanf("%d",&fa[i]);
		llb(fa[i],i);
	}
	dep[1]=1; dg(1);
	fo(i,1,n)g[i][0]=n+1;
	dep[n+1]=n+1;
	fo(i,1,n)fo(l,0,19)g[i][l]=n+1;
	scanf("%d",&m);
	fo(i,1,m){
		scanf("%d%d",&x,&y);
		if(x==y)continue;
		int z=get(x,y);
		if(dep[z]<dep[g[x][0]])g[x][0]=z;
		if(dep[z]<dep[g[y][0]])g[y][0]=z;
		ax[i]=x; ay[i]=y;
	}
	dfs1(1); 
	dfs(1);
	fo(i,1,m)if(ax[i]!=ay[i])llb(ax[i],ay[i]+n),llb(ay[i],ax[i]+n);
	scanf("%d",&q);
	fo(i,1,q){
		scanf("%d%d",&xx[i],&yy[i]);
		int z=get(xx[i],yy[i]);
		ans[i]=get1(xx[i],z)+get1(yy[i],z);
		u1[i]=get2(xx[i],z); v1[i]=fs;
		u2[i]=get2(yy[i],z); v2[i]=fs;
		if(z!=xx[i]&&z!=yy[i])llb(u1[i],i+n+n);
	}
	dfs2(1);
	fo(i,1,q)if(ans[i]>n)puts("-1");else printf("%d\n",ans[i]); 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值