NOI2018 归程

题目大意

%   给出一个 n n n 个点, m m m 条边的无向连通图,每条边上有两个参数 l , a l,a l,a。其中 l l l 表示边的长度, a a a 表示边的海拔(即高度)。对于 Q Q Q个形如 x p 的询问,有如下定义:
  你有一辆车,可以开着它通过任意海拔严格大于 p p p 的边,不需要花费任何代价。除此之外,你可以在车开到某个点后,将这辆车彻底报废(这意味着以后都不能再开车),然后通过任意边而不需要考虑其海拔高度,花费为这条边的长度。
  你需要回答的是从 x x x 号节点开始,开着一辆车,到达节点1需要的最小代价。强制在线。
  数据范围  n ∈ [ 0 , 2 × 1 0 5 ] ∩ Z , m , q ∈ [ 0 , 4 × 1 0 5 ] ∩ Z n\in[0,2\times 10^5]∩\Z,\quad m,q\in[0,4\times 10^5]∩\Z n[0,2×105]Z,m,q[0,4×105]Z
        l ∈ [ 0 , 2 × 1 0 5 ] ∩ Z , a ∈ [ 0 , 1 0 9 ] ∩ Z l\in[0,2\times 10^5]∩\Z,\quad a\in[0,10^9]∩\Z l[0,2×105]Z,a[0,109]Z

题解

%   什么是kruscal重构树?我们当初在用kruscal求最小生成树的时候,维护连通性用的是并查集,这能十分便捷地判断连通性,但由于路径压缩的缘故,我们完美地丢失了合并的关系,考虑将这种关系建立出来。
  当我们在执行算法时,考虑边 ⟨ ( u , v ) , w ⟩ \langle(u,v),w\rangle (u,v),w,我们新建一个点 P P P,然后令 u u u 的祖先和 v v v 的祖先都认 P P P 做祖先,然后将边权存在点 P P P 上作为点 P P P 的点权。
  注意这里的做祖先不是在并查集中执行的,而是要真真实实的建出这样的树来,因此需要再新开一个记录父亲的数组,下图是一个已经建好的例子(最大生成树)。Kt0UmQ.png
  其中Kruscal的顺序为(1,5),(2,3),(3,5),(1,4),即左侧红色线段。
  按照这种方式来建树,可以发现,点数由原来的 n n n 个点变为了 2 n − 1 2n-1 2n1 个,而且上面有很多优秀的性质。如果不看叶子结点,那么这是一个堆,而且点 u u u 到达点 v v v 的路径上的边权最大的路径为点 u u u 和点 v v v 的最近公共祖先的点权。
  回到题目中,我们考虑将原图按照海拔建出这样的树,可以发现,对于询问p x,若点 x x x 的其祖先中,最后一个点权大于 p p p 的点为 q q q,则说明在以 q q q 为根的子树内的所有叶子结点均可开车直接到达。
  由于一个点到根节点的路径上的点的点券单调不上升,因此对于询问 p x,我们用倍增求出点 q q q,然后求出 q q q 子树中那个点距离点1最近即可。这个过程可以用Dijkstra先预处理出1号点到其它点的距离,然后由叶子结点向上合并答案即可。

代码

%   代码经封装,凑合着看。

#include<bits/stdc++.h>
using namespace std;
#define maxn 200010
#define maxm 400010
struct edge{
	int u,v,w,next;
	bool operator<(const edge &x)const{
		return w>x.w;
	}
};
int CNT=0;
struct Gha{
	edge e[maxm<<1];
	int head[maxn],cnt;
	Gha(){init();}
	void init(){memset(head,cnt=0,sizeof head);}
	void ins(int u,int v,int w){
		e[++cnt]=(edge){u,v,w,head[u]};
		head[u]=cnt;
	}
};
struct dijkstra:public Gha{
	dijkstra(){}
	struct node{
		int u,dis;
		node(int a,int b):u(a),dis(b){}
		bool operator<(const node &x)const{return dis>x.dis;}
	};
	void work(int s,int *f,int n){//预处理最短路
		priority_queue<node> q;
		for(int i=1;i<=n;i++) f[i]=0x3f3f3f3fll;
		f[s]=0; q.push(node(s,0));
		while(!q.empty()){
			node x=q.top(); q.pop();
			int u=x.u;
			if(x.dis!=f[u]) continue;
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].v;
				if(e[i].w+f[u]<f[v]){
					q.push(node(v,f[v]=e[i].w+f[u]));
				}
			}
		}
	}
}_D;
struct kurscal{
	edge e[maxm];
	int f[maxn<<1],cnt;
	kurscal():cnt(0){}
	void init(){cnt=0;}
	void ins(int u,int v,int w){e[++cnt]=(edge){u,v,w};}
	int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
	void work(int);
}_K;
int fa[maxn<<1][21],val[maxn<<1];
void kurscal::work(int n){//重构树的过程
	int cntv=n;//新的点的编号
	for(int i=1;i<=2*n;i++) f[i]=i;
	sort(e+1,e+1+cnt);
	int done=0;
	for(int i=1;i<=cnt&&done<n-1;i++){
		int fx=find(e[i].u),fy=find(e[i].v);
		if(fx!=fy) fa[fx][0]=fa[fy][0]=f[fx]=f[fy]=++cntv,val[cntv]=e[i].w,++done;
	}
}
int dis[maxn<<1];
int n,m;
void prefix(void){
	for(int i=n+1;i<=2*n-1;i++) dis[i]=0x3f3f3f3fll;
	for(int i=1;i<=2*n-1;i++)//预处理子树到1号点的最短距离
		dis[fa[i][0]]=min(dis[fa[i][0]],dis[i]);
	for(int j=1;j<=20;j++)//预处理倍增数组
		for(int i=1;i<=2*n-1;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
}
int find(int x,int p){//找x的祖先中最后一个val大于p的节点 
	for(int i=20;i>=0;i--)
		if(fa[x][i]&&val[fa[x][i]]>p) x=fa[x][i];
	return x;
}
inline char nc() {
    static char buf[1000000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &sum) {
    char ch=nc();
    int tf=0;
    sum=0;
    while((ch<'0'||ch>'9')&&(ch!='-'))
        ch=nc();
    tf=((ch=='-')&&(ch=nc()));
    while(ch>='0'&&ch<='9')
        sum=sum*10+(ch-48),ch=nc();
    (tf)&&(sum=-sum);
}
int main(void){
	freopen("return.in","r",stdin);
	freopen("return.out","w",stdout);
	int T;read(T);
	while(T--){
		_D.init(); _K.init();//初始化
		read(n);read(m);
		for(int i=1,u,v,l,a;i<=m;i++){
			read(u);read(v);read(l);read(a);
			_D.ins(u,v,l);_D.ins(v,u,l);_K.ins(u,v,a);
		} _D.work(1,dis,n); _K.work(n); prefix();
		int q,k,s; read(q);read(k);read(s);
		for(int i=1,v0,p0,lastans=0;i<=q;i++){
			read(v0);read(p0);
			int v=(v0+k*lastans-1)%n+1;
			int p=(p0+k*lastans)%(s+1);
			printf("%d\n",lastans=dis[find(v,p)]);
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值