「解题报告」[NOI2018]归程 (Kruskal重构树)

传送

题意

一张 \(n\) 个节点 \(m\) 条边的无向连通图, 每条边有两个属性 : 长度 \(l\), 海拔 \(a\).

\(Q\) 天, 每天给定海拔 \(p\) 和起点 \(v\). 每天都有一辆车, 可以经过 \(a > p\) 的边, 下车后就不能再上车, 须一直步行到点 \(1\).

求从 \(v\)\(1\) 的步行长度最短路径.

强制在线.


思路

离线做法

将询问离线, 按照 \(p\) 从大到小排序. 用并查集把 \(a > p\) 的边的 \((u,v)\) 端点合并, 并记录每个并查集中到 \(1\)\(dis\) 最小值 \(min\_dis\), 询问 \((p,v)\) 的答案即为 \(v\) 所在并查集的 \(min\_dis\).

在线做法

\(Kruskal\) 重构树.

构造方法 : 在 \(Kruskal\) 过程中, 选中边 \((u,v)\) 时, 新建一个虚拟节点 \(x\) 作为 \(u,v\) 在并查集中的根节点 \(fu,fv\) 的共同父节点, 其点权为边 \((u,v)\) 的边权.

性质 :

  1. 重构树中除了叶节点外的任一点到根节点的路径上的点权单调.

    重构树上除了叶节点外的任一点都对应原图上的一条边, 又因为 \(Kruskal\) 算法在选边时存在单调性, 所以易得性质 1.

  2. \(lca(u,v)\) 的所代表的边是原图中 \(u,v\) 两点之间路径的 "瓶颈" (即最小生成树上的最大边 或 最大生成树上的最小边).

实际上, \(Kruskal\) 重构树是一个二叉堆 (叶节点除外).

了解了 \(Kruskal\) 重构树后, 这道题就是个版子了.

首先 \(Dijkstra\) 求出每个点到 \(1\) 的距离 \(dis\).

然后用 \(a\) 作为第一关键字得出原图的最大生成树, 并建出 \(Kruskal\) 重构树, 并记录重构树上子树 \(dis\) 最小值 \(min\_dis\), 并在重构树上建立倍增数组.

处理询问 \((p,v)\) 时, 找到重构树上 \(v\) 的深度最小的且点权大于 \(p\) 的点 \(x\), 答案就为 \(x\) 子树中的 \(min\_dis\).


代码

#include

using namespace std;

#define mkp make_pair

const int _=4e5+7;
const int __=8e5+7;
const int L=20;

int T,n,m,num,dis[_];
int lst[_],nxt[__],to[__],len[__],tot;
bool b[_];
priority_queue > h;
struct edge{
	int u,v,l,a;
}e[_];
struct node{
	int rt,mind,val,ls,rs,f[L+7];
}p[_];

int gi(){
	int x=0; char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x;
}

void Add(int x,int y,int l){
	nxt[++tot]=lst[x]; to[tot]=y; len[tot]=l; lst[x]=tot;
	nxt[++tot]=lst[y]; to[tot]=x; len[tot]=l; lst[y]=tot;
}

void Dijk(){ 
	memset(dis,0x3f,sizeof(dis));
	memset(b,0,sizeof(b));
	dis[1]=0;
	h.push(mkp(0,1));
	while(!h.empty()){
		int u=h.top().second; h.pop();
		if(b[u]) continue;
		b[u]=1;
		for(int i=lst[u];i;i=nxt[i]){
			int v=to[i];
			if(dis[v]>dis[u]+len[i]){
				dis[v]=dis[u]+len[i];
				h.push(mkp(-dis[v],v));
			}
		}
	}
}

int Find(int x){ return p[x].rt==x ?x :p[x].rt=Find(p[x].rt); }

bool cmp(edge a,edge b){ return a.a>b.a; }

void Sta(int u){
	if(!u) return;
	for(int i=1;i<=L;i++)
		p[u].f[i]=p[p[u].f[i-1]].f[i-1];
	Sta(p[u].ls);
	Sta(p[u].rs);
}

void Kruskal(){
	sort(e+1,e+1+m,cmp);
	num=n;
	for(int i=1;i<=n;i++){
		p[i].rt=i;
		p[i].mind=dis[i];
		p[i].ls=p[i].rs=0;
	}

	for(int i=1;i<=m;i++){
		int fu=Find(e[i].u),fv=Find(e[i].v);
		if(fu==fv) continue;
		p[fu].rt=p[fv].rt=p[fu].f[0]=p[fv].f[0]=++num;
		p[num].rt=num;
		p[num].val=e[i].a;
		p[num].ls=fu,p[num].rs=fv;
		p[num].mind=min(p[fu].mind,p[fv].mind);
	}

	Sta(num);
}

void Init(){
	memset(lst,0,sizeof(lst));
	tot=0;
	n=gi(),m=gi();
	for(int i=1;i<=m;i++){
		e[i]=(edge){gi(),gi(),gi(),gi()};
		Add(e[i].u,e[i].v,e[i].l);
	}
	Dijk();
	Kruskal();
}

int Search(int u,int P){
	for(int i=L;i>=0;i--)
		if(p[p[u].f[i]].val>P) u=p[u].f[i];
	return u;
}

void Run(){
	int Q=gi(),K=gi(),S=gi(),v,P,las=0;
	for(int i=1;i<=Q;i++){
		v=(gi()+K*las-1)%n+1;
		P=(gi()+K*las)%(S+1);
		las=p[Search(v,P)].mind;
		printf("%d\n",las);
	}
}

int main(){
	T=gi();
	while(T--){
		Init();
		Run();
	}
	return 0;
}

你可能感兴趣的:(「解题报告」[NOI2018]归程 (Kruskal重构树))