洛谷4768 NOI2018 归程 Kruskal重构树 最短路

题目链接

题意:
给你一个n个点m条边的无向图,每个点有两个权值,一个是长度一个是海拔。有q次询问,你在起点有一辆车,车可以在海拔高于l的路走,其余的路只能走过去,每次询问从x到1号点最少行走多少距离。n,m,q都是1e5量级的。强制在线。

题解:
没想到我在飞机上口胡了一个做法,竟然还真的把这个题过了。我这种蒟蒻做出NOI题了?

有了之前那个IOI的题,这个题就简单不少了。我们先预处理一个1号点到每个点的最短路。然后我们还是求一个Kruskal重构树,边按照海拔从大到小加入,因为海拔高的可以经过时海拔更低的才有可能可以经过。 我们求的过程中不仅记录每个节点的海拔,还记录这个节点为根的子树内到1号点最短路的最小值。这样我们就可在询问时在Kruskal重构树上倍增,然后找到正好不超过海拔限制的树上的点,然后这个点的记录的子树到1号点的最小值就是答案。

复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,qq,k,s,ans,hed[400010],cnt,hed1[400010],cnt1,T;
int dis[400010],vis[400010],fa[400010],f[400010][21],dep[400010],mn[400010];
int num,val[400010];
priority_queue<pair<int,int> > q;
struct node
{
	int to,next,a,l,from;
}a[800010],b[800010];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void add(int from,int to,int l,int x)
{
	a[++cnt].to=to;
	a[cnt].from=from;
	a[cnt].next=hed[from];
	a[cnt].l=l;
	a[cnt].a=x;
	hed[from]=cnt;
}
inline int cmp(node x,node y)
{
	return x.a>y.a;
}
inline int getr(int x)
{
	if(x==fa[x])
	return x;
	else
	{
		fa[x]=getr(fa[x]);
		return fa[x];
	}
}
inline void add1(int from,int to)
{
	b[++cnt1].to=to;
	b[cnt1].next=hed1[from];
	hed1[from]=cnt1;
}
inline void dfs(int x)
{
	for(int i=1;i<=20;++i)
	f[x][i]=f[f[x][i-1]][i-1];
	for(int i=hed1[x];i;i=b[i].next)
	{
		int y=b[i].to;
		if(y==f[x][0])
		continue;
		f[y][0]=x;
		dep[y]=dep[x]+1;
		dfs(y);
	}
}
inline int lca(int x,int y)
{
	for(int i=20;i>=0;--i)
	{
		if(val[f[x][i]]>y)
		x=f[x][i];
	}
	return x;
}
int main()
{
	T=read();
	while(T--)
	{
		memset(hed,0,sizeof(hed));
		for(int i=1;i<=cnt;++i)
		{
			a[i].to=0;
			a[i].next=0;
			a[i].a=0;
			a[i].l=0;
		}
		cnt=0;
		memset(hed1,0,sizeof(hed1));
		for(int i=1;i<=cnt1;++i)
		{
			b[i].to=0;
			b[i].next=0;
			b[i].a=0;
			b[i].l=0;
		}
		memset(mn,0,sizeof(mn));
		memset(val,0,sizeof(val));
		cnt1=0;
		n=read();
		m=read();
		for(int i=1;i<=m;++i)
		{
			int x,y,l,a;
			x=read();
			y=read();
			l=read();
			a=read();
			add(x,y,l,a);
			add(y,x,l,a);
		}
		q.push(make_pair(0,1));
		memset(dis,0x3f,sizeof(dis));
		memset(vis,0,sizeof(vis));
		dis[1]=0;
		while(!q.empty())
		{
			int x=q.top().second;
			q.pop();
			if(vis[x])
			continue;
			vis[x]=1;
			for(int i=hed[x];i;i=a[i].next)
			{
				int y=a[i].to;
				if(dis[y]>dis[x]+a[i].l)
				{
					dis[y]=dis[x]+a[i].l;
					q.push(make_pair(-dis[y],y));
				}
			}
		}
		sort(a+1,a+cnt+1,cmp);
		for(int i=1;i<=2*n;++i)
		fa[i]=i;
		for(int i=1;i<=n;++i)
		{
			mn[i]=dis[i];
			val[i]=2e9;
		}	
		num=n;
		for(int i=1;i<=cnt;++i)
		{
			int fx=getr(a[i].from),fy=getr(a[i].to);
			if(fx!=fy)
			{
				++num;
				add1(num,fx);
				add1(fx,num);
				add1(fy,num);
				add1(num,fy);
				mn[num]=min(mn[fx],mn[fy]);
				val[num]=a[i].a;
				fa[fx]=fa[fy]=num;
			}
		}
		memset(f,0,sizeof(f));
		memset(dep,0,sizeof(dep));
		dep[num]=1;
		dfs(num);
		ans=0;
		qq=read();
		k=read();
		s=read();
		for(int i=1;i<=qq;++i)
		{
			int x,y,z;
			x=read();
			y=read();
			x=(x+k*ans-1)%n+1;
			y=(y+k*ans)%(s+1);
			z=lca(x,y);
			ans=mn[z];
			printf("%d\n",ans);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值