[题解]NOI2018 归程

传送门

咕了好多天了,先是打了离线暴力,后来学克鲁斯卡尔重构树,然后又咕了好久才打完

题目描述

一张\(n\)个点,\(m\)条边的无向图,每条边有一个长度\(l\)和一个海拔\(a\),每天会有一个水位线,\(a\)小于等于水位线的都会被淹没

\(Q\)个询问:

询问\(u,p\):问从\(u\)开始走没被淹没的边所能到达的点中,离1号点的最小距离

强制在线

分析

发现把题目转换过来之后好像还真的是个板子题

先考虑离线做法:

从高到低枚举水位线,对于当前的水位线,如果一条道路目前不会被淹没,那么之后也不会被淹没,那么我们可以用并查集维护相互之间走没有被淹没的路可以到达的点集,同一个点集中的点到1要走的路就是点集中的点到1的最小距离

再考虑强制在线:

先跑一遍最短路求出每个点到\(1\)的最短距离这个很显然了

可以发现首先需要维护从每个点开始走\(a\)值大于\(p\)的边所能到达的点,这个可以用克鲁斯卡尔重构树:

按照\(a\)从大到构建重构树,那么两个点的\(LCA\)就是他们之间最小海拔的最大值,也就是说,不管怎么走,从\(u\)\(v\)必然会经过一条海拔小于等于\(val[LCA(u,v)]\)的边,那么我们只需要在克鲁斯卡尔重构树上维护每一个点的子树中的所有点到1的距离的最小值。

查询的时候从\(u\)往上跳,找到最高的权值大于\(p\)的点,\(u\)所能到的点就是这个点子树中的叶子节点

那么只需要来一个倍增就可以啦

代码

#include<bits/stdc++.h>
#define rep(X,A,B) for(int X=A;X<=B;X++)
#define tep(X,A,B) for(int X=A;X>=B;X--)
#define pb(X) push_back(X)
#define LL long long
const int MX=30;
const int N=200010;
const int M=400010;
const LL INF=1e16;
using namespace std;

struct ed{
	int u,v,h;
}E[M];

int cmp(ed A,ed B){
	return A.h>B.h;
};
//---
struct nn{
	int id;
	LL dis;
	
	bool operator < (const nn &A)const{
		return dis > A.dis;
	}
};

priority_queue<nn>Q;
//---
LL dis[N<<1],las;
int n,m,op,S;
int fa[N<<1][MX],dep[N<<1],son[N<<1][2];
int edge[M<<1],lst[N],nxt[M<<1],wei[M<<1],t=0;
int pa[N],tot,vis[N],alt[N];

void MEM(){
	memset(lst,0,sizeof(lst));
	t=0;las=0;
	memset(fa,0,sizeof(fa));
}

void ADD(int x,int y,int z){
	edge[++t]=y;nxt[t]=lst[x];lst[x]=t;wei[t]=z;
}

int GETFA(int x){
	if(pa[x]!=x)pa[x]=GETFA(pa[x]);
	return pa[x];
}

void READ(){
	int u,v,w,h;
	scanf("%d%d",&n,&m);
	rep(i,1,m){
		scanf("%d%d%d%d",&u,&v,&w,&h);
		ADD(u,v,w);
		ADD(v,u,w);
		E[i]=(ed){u,v,h};
	}
}

void DIJ(){
	rep(i,1,n)dis[i]=INF,vis[i]=0;
	dis[1]=0;Q.push((nn){1,0});
	while(!Q.empty()){
		int x=Q.top().id;Q.pop();
		if(vis[x])continue;
		for(int r=lst[x];r;r=nxt[r]){
			if(dis[edge[r]]>dis[x]+wei[r]){
				dis[edge[r]]=dis[x]+wei[r];
				Q.push((nn){edge[r],dis[edge[r]]});
			}
		}
		vis[x]=1;
	}
}

void BUILD(){
	sort(E+1,E+m+1,cmp);
	rep(i,1,n)pa[i]=i;
	tot=n;
	rep(i,1,m){
		int fu=GETFA(E[i].u),fv=GETFA(E[i].v);
		if(fu==fv)continue;
		tot++;
		pa[tot]=tot;
		alt[tot]=E[i].h;
		son[tot][0]=fu;
		son[tot][1]=fv;
		dis[tot]=min(dis[son[tot][0]],dis[son[tot][1]]);
		fa[fu][0]=fa[fv][0]=tot;
		pa[fu]=pa[fv]=tot;
	}
	
	fa[tot][0]=0;dep[0]=0;
	tep(x,tot,1)dep[x]=dep[fa[x][0]]+1;
	rep(i,1,20){
		rep(x,1,tot){
			if(dep[x]<=(1<<i))continue;
			fa[x][i]=fa[fa[x][i-1]][i-1];
		}
	}
}

void PRT(int x){
	if(!x)return ;
//	printf("dis[%d]=%lld alt[%d]=%d dep[%d]=%d son[%d]=%d %d\n",x,dis[x],x,alt[x],x,dep[x],x,son[x][0],son[x][1]);
//	for(int i=0;(1<<i)<dep[x];i++)printf("	fa[%d][%d]=%d\n",x,i,fa[x][i]);
	PRT(son[x][0]);
	PRT(son[x][1]);
}

LL SOLVE(){
	LL u,p;
	scanf("%lld%lld",&u,&p);
	u=(u+op*las-1)%n+1;
	p=(p+op*las)%(S+1);
	int pos=u;
	for(int i=20;i>=0;i--){
		if((1<<i)>=dep[pos])continue;
		if(alt[fa[pos][i]]<=p)continue;
		pos=fa[pos][i];
	}
	//printf("pos=%d\n",pos);
	return las=dis[pos];
}

void SSOLVE(){
	READ();
	DIJ();
	BUILD();
	//PRT(tot);
	int G;las=0;
	scanf("%d%d%d",&G,&op,&S);
	while(G--)printf("%lld\n",SOLVE());
}

void OvO(){
	freopen("return.in","r",stdin);
	freopen("return.out","w",stdout);
}

int main(){
	//OvO();
	int T;
	scanf("%d",&T);
	while(T--){
		MEM();//!!!!!!!!!!!!!!!!!!!!!!!!!
		SSOLVE();
	}
	return 0;
}

总结

突然想到虽然是NOI但也是D1T1呀,难怪那么裸

主要就是简化题意吧

然后呢,倍增是个好东西

posted @ 2019-10-28 14:55  硫氯  阅读(83)  评论(0编辑  收藏  举报