P4768 [NOI2018]歸程(Kruskal重構樹,倍增,單源最短路徑)

求求你不要再封裝了!struct封裝鍋很多的!\color{#ee0000} {\LARGE\text{求求你不要再封裝了!struct封裝鍋很多的!}}

發現Kruskal重構樹和倍增通常一起使用呢!

P4768 [NOI2018]歸程

題目描述

自己看題面吧,不可描述.

題目分析

從大體思路入手, 我們可以把從v到1的路徑分成兩部分, 一半全開車,一半全走路.

也就是說要枚舉n個節點作爲斷點(假設當前斷點爲u), 這個斷點是可行解與最優解當且僅當

存在一條從v到u的路徑可以全部開車
且從u到1全部走路的最短路是滿足上一條件中最短的

那麼要怎麼求出從v出發可以開車到的點呢, 顯然從v出發開車可以到的點, 一定滿足路徑上所有邊海拔都高於水位,

這裏已經很明顯可以用Kruskal重構樹求解了.

KruskalKruskal 重構樹

“查詢從某個點出發經過邊權不超過val的邊所能到達的節點”,諸如此類的題目,第一反應應該就是KruskalKruskal重構樹.

由於本題中有水面高度的限制,不妨以每條邊的海拔高度爲關鍵字對邊進行由大到小排序,這樣的重構樹就是一棵小根堆,到時候找大於水面高度的最小值就可以了.

他的子樹的全部節點都可以由v開車到達. (由重構樹性質得證)

倍增

同樣的套路,找水面高度的最小值,用倍增顯然快.


以上我們就處理出了大於水面高度的所有點. 那麼要到點1的距離最小,是不是就是在這些點裏面選呢?所以要處理出點1到大於水面高度的每個點的最短路徑,然後再從這些點裏面選擇到點1距離最小的點.

單源最短路徑

顯然每個點到1的最短路徑是一定的,可以預處理.

不要用spfaspfa! “它死了”,就在這道題!


我們發現,由重構樹的性質,能相互通達的點一定有共同的LCA,所以LCA可以維護到點1的路徑的最小值,只需在回溯時更新父親的最小值即可.

程序實現

最後捋一捋思路:我們首先要求出每個點到1號點的最小花費,這個直接dijstradijstra最短路預處理. 然後是要建出重構樹,再然後維護以每個點作爲根節點時子樹中距離1號點的最小花費,這個建完樹後一個簡單的dfs搞定. 最後是如何找到點u,這時我們要讓一個重要的算法登場:倍增算法. 直接加上點權>p的限制在樹上倍增即可.

聽起來很順?你以爲這道題那麼簡單?不!來看看我這兩天碰上的玄學錯誤吧

#include<bits/stdc++.h>
#define maxn 2000010
using namespace std;
int head[maxn],tot;//用於求最短路 
int tree_head[maxn],sum;//用於重構樹 
int n,m;
int f[maxn],fa[maxn][25],dif[maxn];
struct Edge{
	int u,v,len,height,next;
	void add(int x,int y,int l,int h){u=x,v=y,len=l,height=h;}
	void add_edge(int x,int y,int l,int h){
		u=x,v=y,len=l,height=h;
		next=head[u];
		head[u]=tot;
	}
//	void _add(int x,int y){
//		v=y;
//		next=tree_head[u];
//		tree_head[u]=sum;
//		printf("%d\n",tree_head[u]);
//		printf("%d to %d",x,y);
//		printf("all right\n");
//	}
	bool operator <(Edge path)const{
		return path.height <height;
	}
};
Edge e[maxn];//用於求最短路 
Edge E[maxn];//用於構建Kruskal樹 
Edge ed[maxn];//用於存儲輸入數據 
struct node{
	int pos,dis;
	bool operator <(node nd)const {
		return dis>nd.dis ;
	}
};
priority_queue<node >q;
bool vis[maxn];
int dis[maxn];
void dijkstra(){
	dis[1]=0;
	q.push((node){1,0});
	while(!q.empty()){
		node now=q.top();
		q.pop();
		int u=now.pos ;
		if(vis[u])continue;
		vis[u]=true;
		for(int i=head[u];i;i=e[i].next ){
			int v=e[i].v ; 
			if(dis[v]>dis[u]+e[i].len){
				dis[v]=dis[u]+e[i].len ;
				if(!vis[v])q.push((node){v,dis[v]});
			}
		}
	}
}//預處理最短路
void _add(int x,int y){
	E[++sum].v =y;
	E[sum].next =tree_head[x];
	tree_head[x]=sum;
}
void dfs(int u){
	//printf("now is %d\n",u);
	for(int i=1;i<=20;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
	//if(!tree_head[u])return ;
	for(int i=tree_head[u];i;i=E[i].next ){
		int v=E[i].v ;
		dfs(v);
		//printf("%d\n",dis[v]);
		dis[u]=min(dis[u],dis[v]);
	}
	return ;
}//dfs維護距離最小值和fa的倍增關係
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
int num;
void kruskal(){
	for(int i=1;i<=n;i++)f[i]=i;
	sort(ed+1,ed+m+1);
	int cnt=n;
	for(int i=1;i<=m;i++){
		int u=ed[i].u ,v=ed[i].v ;
		int fu=find(u),fv=find(v);
		if(fu==fv)continue;
		dif[++cnt]=ed[i].height;
		f[fu]=f[fv]=f[cnt]=cnt;
		fa[fu][0]=fa[fv][0]=cnt;
//		E[++sum]._add(cnt,fu);
//		E[++sum]._add(cnt,fv);
		_add(cnt,fu);
		_add(cnt,fv);//不解釋
	}
//	num=cnt;
//	for(int i=1;i<=num;i++)printf("%d ",tree_head[i]);
//	printf("\n");
	dfs(cnt);
//	for(int i=1;i<=cnt;i++)printf("%d ",dis[i]);
//	printf("\n");
}
int main(){
	int T;
	scanf("%d",&T);
	for(int up=1;up<=T;up++){
		memset(e,0,sizeof e);
		memset(ed,0,sizeof ed);
		memset(E,0,sizeof E);
		memset(vis,0,sizeof vis);
		memset(tree_head,0,sizeof tree_head);
		memset(head,0,sizeof head);
		//f,fa,dif
		memset(f,0,sizeof f);
		memset(fa,0,sizeof fa);
		memset(dif,0,sizeof dif);
	scanf("%d%d",&n,&m);
	for(int i=1,u,v,l,a;i<=m;i++){
		scanf("%d%d%d%d",&u,&v,&l,&a);
		ed[i].add(u,v,l,a);
		e[++tot].add_edge(u,v,l,a);
		e[++tot].add_edge(v,u,l,a); 
	}
	//for(int i=1;i<=100;i++)dis[i]=10000;
	memset(dis,0x3f3f3f3f,sizeof dis);
	dijkstra();
//	dfs(num);
	kruskal();
//	for(int i=1;i<=num;i++)printf("%d ",dis[i]);
//	printf("\n");
	int q,k,s,lastans=0;
	scanf("%d%d%d",&q,&k,&s);
	for(int i=1,v,p;i<=q;i++){
		scanf("%d%d",&v,&p);
		v=(v+k*lastans-1)%n+1;
		p=(p+k*lastans)%(s+1);//在線
		for(int i=20;i>=0;i--){
			if(fa[v][i]&&dif[fa[v][i]]>p)v=fa[v][i];//倍增找最大的滿足條件的點
		}
		printf("%d\n",dis[v]);
		lastans=dis[v];//記得lastans賦值
	}}
	return 0;
}

upd:我下午又打了一遍,結果更加玄學了。。。這一份76pts

這不完全一樣嗎!!!!?????

#include<bits/stdc++.h>
#define maxn 2000010
//#define maxn 1600010
//#define maxn 800010
using namespace std;
int tot,head[maxn];
int sum,tree_head[maxn];
int n,m;
struct Edge{
	int u,v,len,height,next;
	void add(int x,int y,int l,int h){u=x;v=y;len=l;height=h;}
//	void add_edge(int x,int y,int l){
//		v=y;len=l;next=head[u];
//		head[u]=tot;
//	}
//	void _add(int x,int y){
//		v=y;next=tree_head[x];
//		tree_head[x]=sum;
//	}
	bool operator <(Edge path)const {
		return path.height <height ;
	}
};
Edge ed[maxn];
Edge e[maxn];
Edge E[maxn];
void add_edge(int x,int y,int l){
	e[++tot].v =y;
	e[tot].len =l;
	e[tot].next =head[x];
	head[x]=tot;
}
bool vis[maxn];
int dis[maxn];
struct node{
	int pos,dis;
	bool operator <(node nd)const {
		return dis>nd.dis ;
	}
};
priority_queue<node>q;
void dijkstra(){
	dis[1]=0;
	q.push((node){1,0});
	while(!q.empty()){
		node now=q.top();
		q.pop();
		int u=now.pos ;
		if(vis[u])continue;
		vis[u]=true;
		for(int i=head[u];i;i=e[i].next ){
			int v=e[i].v ;
			if(dis[v]>dis[u]+e[i].len ){
				dis[v]=dis[u]+e[i].len ;
				if(!vis[v]){
					q.push((node){v,dis[v]});
				}
			}
		}
	}
}
void _add(int x,int y){
	E[++sum].v =y;
	E[sum].next =tree_head[x];
	tree_head[x]=sum;
}
int fa[maxn][25],f[maxn],dif[maxn];
void dfs(int u){
	for(int i=1;i<=20;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	if(!tree_head[u])return ;
	for(int i=tree_head[u];i;i=E[i].next ){
		int v=E[i].v ;
		dfs(v);
		dis[u]=min(dis[u],dis[v]);
	}
	return ;
}
//int num;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void kruskal(){
	for(int i=1;i<=n;i++)f[i]=i;
	sort(ed+1,ed+m+1);
	int cnt=n;
	for(int i=1;i<=m;i++){
		int fu=find(ed[i].u );
		int fv=find(ed[i].v );
		if(fu==fv)continue;
		dif[++cnt]=ed[i].height ;
		f[fu]=f[fv]=f[cnt]=cnt;
		fa[fu][0]=fa[fv][0]=cnt;
//		E[++sum]._add(cnt,fu);
//		E[++sum]._add(cnt,fv);  
		_add(cnt,fu);
		_add(cnt,fv);
	}
	dfs(cnt);
//	num=cnt;
}
void init(){
	memset(head,0,sizeof head);
	memset(tree_head,0,sizeof tree_head);
	memset(E,0,sizeof E);
	memset(ed,0,sizeof ed);
	memset(e,0,sizeof e);
	memset(vis,0,sizeof vis);
	memset(dis,0,sizeof dis);
	memset(f,0,sizeof f);
	memset(fa,0,sizeof fa);
	memset(dif,0,sizeof dif);
}
int main(){
	int T;
	scanf("%d",&T);
	for(int fr=1;fr<=T;fr++){
		init();
		scanf("%d%d",&n,&m);
		for(int i=1,u,v,l,a;i<=m;i++){
			scanf("%d%d%d%d",&u,&v,&l,&a);
			ed[i].add(u,v,l,a); 
//			e[++tot].add_edge(u,v,l);
//			e[++tot].add_edge(v,u,l); 
			add_edge(u,v,l);
			add_edge(v,u,l);
		}
		memset(dis,0x3f3f3f3f,sizeof dis);
		dijkstra();
		kruskal();
//		for(int i=1;i<=num;i++)printf("%d ",dis[i]);
//		printf("\n");
		int q,k,s,lastans=0;
		scanf("%d%d%d",&q,&k,&s);
		for(int i=1,v,p;i<=q;i++){
			scanf("%d%d",&v,&p);
			v=(v+k*lastans-1)%n+1;
			p=(p+k*lastans)%(s+1);
			for(int i=20;i>=0;i--){
				if(fa[v][i]&&dif[fa[v][i]]>p)v=fa[v][i];
			}
			printf("%d\n",dis[v]);
			lastans=dis[v];
		}
	}
	return 0;
}

題後總結

在這裏插入圖片描述我真的連數組大小分析都做不來了麼。。。

思路參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章