【题解】P4768 [NOI2018] 归程(树上倍增,Kruskal 重构树,dijkstra)

【题解】P4768 [NOI2018] 归程

作为一道 NOI 的 D1T1,放在现在或许难度不够(?),但可能在 Kruskal 重构树还未普及的当时,还是相对来说比较难的一道题吧。

写篇题解记录一下。


题目链接

P4768 [NOI2018] 归程

题意概述

给定一张 \(n\) 个点 \(m\) 条边的无向图,对于每条边有两个参数:长度 \(l\)海拔 \(a\)

\(Q\) 组询问,每组询问给定 \(v,p\),表示每一天以 \(v\) 作为起点,\(1\) 作为终点,问从走到的第一条海拔 \(\le p\) 的边开始,在它及它之后走到的所有边的边权之和最小是多少。

本题多测。

思路分析

考虑将每一天的所有路程分为两部分:先从 \(v\) 走到最后一条海拔 \(> p\) 的边 \(u\),再从 \(u\) 走到 \(1\)

题目要求的就是从 \(u\)\(1\) 的边权之和最小是多少。

我们首先思考这里的 \(u\) 需要满足什么条件。

有以下两点:

  • \(v\) 不经过海拔 \(\le p\) 的边能够到达 \(u\)
  • \(u\)\(1\) 的边权和最小。

对于第一点,可以发现它其实是一个比较板的 Kruskal 重构树的模型。

由于是“不经过海拔 \(\le p\) 的边”,那么实际上就相当于“经过的边只有海拔 \(>p\) 的”。

以海拔为边权建图,问题就转化为,从 \(v\) 开始,只经过边权 \(>p\) 的边能够到达哪些点。

显然我们可以直接将边权从大到小排序,然后建立 Kruskal 重构树。

若不考虑边权 \(>p\) 的限制,那么对于一个点 \(u\) 能够到达的点就是和它在一个联通块内的所有点。

再考虑边权的限制,根据 Kruskal 的性质,从叶子节点到根节点一路往上走,点权一定是递减的。

那么根据这个性质,从一个点 \(u\) 出发能够到达的边权 \(>p\) 的点一定是从 \(u\) 到根节点的路径上的某个点 \(x\) 的子树内所有的点。且这个 \(x\) 的点权一定是从 \(u\) 走到根节点的路径上,最后一个点权 \(>p\) 的。

换句话说,假如从 \(u\) 到根节点的路径上,\(x\) 的点权 \(>p\),且 \(x\) 之后点的点权都 \(\le p\),那么 \(x\) 的子树内的所有点就是符合题意的点。

这一点很容易证明:

  • 首先证明 \(x\) 子树内的所有点一定满足题意。这个根据 Kruskal 的性质,即若 \(x\) 的子树中存在两点 \(u,v\),那么实际的图上 \(u\)\(v\) 的路径上最小边权的最大值一定等于 \(x\) 的点权。所以 \(x\) 的点权是其子树中任意两点的路径上的最小边权最大值,那么既然最小边权都 \(>p\) 了,那么其它的也一定 \(>p\),所以满足题意;
  • 再证明从 \(u\) 到根节点的路径上的其它点都不满足题意。
    • 对于 \(x\) 之前的点,显然 \(x\) 的子树完全包含它之前的点,那么显然将答案设在 \(x\) 之前的点会漏掉一些点,所以不优。
    • 对于 \(x\) 之后的点,由于其点权一定 \(\le p\),那么其子树内的任意两点的最小边权最大值一定 \(\le p\),不符合要求边权 \(>p\) 的限制,所以只有 \(x\) 子树内的点满足条件。

那么证明了答案在 \(x\),那么怎么找到 \(x\) 呢。其实可以直接树上倍增。

预处理出来一个数组 \(fa_{x,i}\) 表示从 \(x\) 向上走 \(2^i\) 步能够到哪。然后我们往上倍增的跳,假设当前在 \(now\),只要 \(fa_{now,i}\) 的点权 \(>p\) 就跳,反之不跳。即可找到满足条件的 \(x\)

我们找到了 \(x\),相当于只完成了第一点。

即符合第一个条件的点一定在 \(x\) 的子树内。考虑第二点,满足条件的 \(u\) 就是 \(x\) 子树内到 \(1\) 的最短路最小的点。

从一个点到 \(1\) 的最短路可以直接建立反图 dijkstra 求出。

而如何判断 \(x\) 子树内哪个点距离 \(1\) 最近呢?

直接暴力枚举吗?显然时间复杂度炸了。

实际上我们可以直接处理出来一个数组 \(mi_{now}\) 表示 \(now\) 所在子树内距离 \(1\) 最近的点的距离是多少。

由于重构树是个二叉树,那么可以直接用类似线段树 pushup 的操作求,即 \(mi_{now}=\min\{mi_{now},mi_{ls},mi_{rs}\}\)

总复杂度 \(O(T n\log n)\)

易错点

  • 注意多测清空;
  • 注意有些数组要开二倍空间;
  • 注意重构树的边比原树多了 \(n-1\) 条;
  • 注意强制在线 \(lastans\) 变量的更新;

代码实现

//luoguP4768
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#define int long long
using namespace std;
const int maxn=2e5+10;
int fa[maxn<<1],vis[maxn<<1],dis[maxn],sum[maxn<<1],val[maxn<<1];
int mi[maxn<<1][25],f[maxn<<1][25],dep[maxn<<1];
int n,m,tot;

struct nod{int u,v,w;}e[maxn<<1];

struct Node{
	int v,w;
	bool operator<(const Node &t)const
	{
		return w>t.w;
	}
};

basic_string<Node>edge[maxn];
basic_string<int>edge2[maxn<<1];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

int cmp(nod a,nod b){return a.w>b.w;}

int find(int x)
{
	if(x!=fa[x])fa[x]=find(fa[x]);
	return fa[x];
}

void add(int x,int y)
{
	x=find(x);y=find(y);
	fa[x]=y;
}

void dfs(int now)
{
	for(int nxt:edge2[now])
	{
		dfs(nxt);
		sum[now]=min(sum[now],sum[nxt]);
	}
}

void Kruskal()
{
	for(int i=1;i<=(n<<1);i++)fa[i]=i;
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		if(find(e[i].u)==find(e[i].v))continue;
		int x=find(e[i].u),y=find(e[i].v);
		add(e[i].u,e[i].v);
		val[++tot]=e[i].w;
		fa[x]=fa[y]=fa[tot]=tot;
		edge2[tot]+=x;edge2[tot]+=y;
	}
}

void dijkstra(int s)
{
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)dis[i]=(1ll<<60);
	dis[s]=0;
	priority_queue<Node>q;
	q.push(Node{s,dis[s]});
	while(!q.empty())
	{
		Node now=q.top();
		q.pop();
		if(vis[now.v])continue;
		vis[now.v]++;
		for(Node y:edge[now.v])
		{
			if(dis[y.v]>dis[now.v]+y.w)
			{
				dis[y.v]=dis[now.v]+y.w;
				q.push(Node{y.v,dis[y.v]});
			}
		}
	}
	for(int i=1;i<=n;i++)sum[i]=dis[i];
}

void dfs2(int x,int fath)
{
	dep[x]=dep[fath]+1;
	mi[x][0]=val[fa[x]];
	f[x][0]=fath;
	for(int i=1;i<=20;i++)
	{
		f[x][i]=f[f[x][i-1]][i-1];
		mi[x][i]=min(mi[x][i],mi[f[x][i-1]][i-1]);
	}
	for(int y:edge2[x])
	{
		if(y==fath)continue;
		dfs2(y,x);
	}
}

signed main()
{
	int T=read();
	while(T--)
	{
		for(int i=1;i<=n;i++)edge[i].clear();
		for(int i=1;i<=(n<<1);i++)edge2[i].clear();
		memset(dep,0,sizeof(dep));
		n=read();m=read();
		for(int i=1;i<=m;i++)
		{
			int u,v,l,a;
			u=read();v=read();l=read();a=read();
			e[i]=nod{u,v,a};
			edge[u]+=Node{v,l};
			edge[v]+=Node{u,l};
		}
		dijkstra(1);
		tot=n;
		Kruskal();
		for(int i=n+1;i<=(n<<1);i++)sum[i]=(1ll<<60);
		dfs(tot);
		dfs2(tot,0);
		int q=read(),k=read(),s=read();
		int lastans=0;
		while(q--)
		{
			int v0=read(),p0=read();
			int v=(v0+k*lastans-1)%n+1;
			int p=(p0+k*lastans)%(s+1);
			int now=v;
			for(int i=20;i>=0;i--)
			{
				int cha=dep[now]-dep[tot];
				if((cha>=(1<<i))&&val[f[now][i]]>p)
				{
					now=f[now][i];
				}
			}
			cout<<sum[now]<<'\n';
			lastans=sum[now];
		}
	}
	return 0;
}
posted @ 2023-01-17 22:31  向日葵Reta  阅读(31)  评论(0编辑  收藏  举报