zoukankan      html  css  js  c++  java
  • 【题解】Kruskal重构树——[NOI2018] 归程

    题目链接: UOJ LOJ

    感觉 Kruskal 重构树比较简单,就不单独开学习笔记了。

    Statement

    给定一个 (n)(m) 边的无向连通图,用 (l,a) 描述一条边的长度、海拔。用水位线描述降雨,所有海拔不超过水位线的边都是有积水的。

    (q) 次询问,每次给出出发点 (v) 和水位线 (p) ,开始会有一辆车,可以经过任意没有积水的边,可以在任意节点下车步行(车不会延续到下一次)。问回到 (1) 号点最小的步行长度。强制在线。

    (nleq 2e5,mleq 4e5,qleq 4e5,1leq Sleq 1e9,lleq 1e4,aleq 1e9) .

    Solution

    名字挺高级的,事实上很简单。

    重点解决的主要问题: 查询从某个点出发经过边权不超过 val 的边所能到达的节点

    大致就是类似 点分治=>点分树 的思路,点分树是把点分治中所有分治重心按照层次关系连接起来形成树状结构,那么 Kruskal 重构树就是在 Kruskal 建最小生成树的时候,不直接合并两个点,而是新建一个虚点进行合并。好吧,看上去一点都不像

    具体一点就看 这个博客 .

    性质:一个点的所有子树节点的权值都不大于它的权值,并且从它开始逐渐向子节点移动,权值是单调不增的。

    Proof

    显然,考虑 Kruskal 的加边过程即可。

    查询的时候进行树上倍增,得到能够到达的最远祖先,那么根据限制,能到达的连通块中的节点就是这个祖先点的子树中所有的叶节点。(除了叶节点都是虚点嘛)


    回到这道题目。题目要求就是要将一条 (v o 1) 的路径分成两部分,设断点为 (u) ,要满足 (u o v) 的路径上所有边的海拔大于 (p) ,在此前提下 (1 o u) 最短。

    那么很容易发现,合法 (u) 构成的点集在原图的最大生成树上。

    根据上面的铺垫,容易想到使用 Kruskal 重构树:

    把每条边按照海拔降序,求出重构树。按照上面的步骤,对于每个询问,树上倍增得到包含起点 (v) 的子树中根节点深度最小且海拔大于 (p) 的子树 (x) ,那么合法 (u) 的集合就是 (x) 子树内的所有叶子节点。现在就要在这些点中找出到点 (1) 的最短路。这个同样可以在 Kruskal 中求解出路径,并在每个节点统计子树内的最短路径长度即可,复杂度是 (mathcal{O}(1)) 的。

    总复杂度为 (mathcal{O}(T imes nlog n)) .

    Code

    过不去 UOJ Extra Test 的多半是没开 long long.

    //Author: RingweEH
    const int N=2e5+10;
    struct edge
    {
    	int to,nxt,val;
    }e[N<<2];
    struct node
    {
    	int u; ll dis;
    	bool operator < ( const node &tmp ) const { return dis>tmp.dis; }
    };
    struct edge2
    {
    	int fro,to,val;
    	bool operator < ( const edge2 &tmp ) const { return val>tmp.val; }
    }e2[N<<1];
    int n,m,q,k,s,tot=0,head[N],fath[N<<1],fa[N<<1][21],a[N<<1],siz;
    ll dis[N<<1],lasans;
    
    void add( int u,int v,int w )
    {
    	e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].val=w;
    }
    
    void Dijkstra()
    {
    	priority_queue<node> q;
    	for ( int i=1; i<=n*2-1; i++ )
    		dis[i]=1e15;
    	dis[1]=0; q.push( (node){1,0} );
    	while ( !q.empty() )
    	{
    		node u=q.top(); q.pop();
    		if ( u.dis>dis[u.u] ) continue;
    		for ( int i=head[u.u]; i; i=e[i].nxt )
    			if ( u.dis+e[i].val<dis[e[i].to] ) q.push( (node){e[i].to,dis[e[i].to]=u.dis+e[i].val} );
    	}
    }
    
    int find( int x )
    {
    	return x==fath[x] ? x : fath[x]=find(fath[x]);
    }
    
    void Kruskal()
    {
    	memset( fa,0,sizeof(fa) ); siz=n;
    	for ( int i=1; i<n*2; i++ )	
    		fath[i]=i;
    	sort( e2+1,e2+1+m );
    	for ( int i=1; i<=m; i++ )
    	{
    		int u=find( e2[i].fro ),v=find( e2[i].to );
    		if ( u==v ) continue;
    		fa[u][0]=fath[u]=fa[v][0]=fath[v]=++siz;
    		a[siz]=e2[i].val; dis[siz]=min( dis[u],dis[v] );
    		if ( siz==n*2-1 ) break;
    	}
    	for ( int i=siz; i>=1; i-- )
    		for ( int j=1; j<=18; j++ )
    			fa[i][j]=fa[fa[i][j-1]][j-1];
    }
    
    int main()
    {
    	freopen( "return.in","r",stdin ); freopen( "return.out","w",stdout );
    
    	int T=read();
    	while ( T-- )
    	{
    		memset( head,0,sizeof(head) ); tot=lasans=0;
    
    		n=read(); m=read();
    		for ( int i=1; i<=m; i++ )
    		{
    			e2[i].fro=read(),e2[i].to=read(); int w=read(); e2[i].val=read();
    			add( e2[i].fro,e2[i].to,w ); add( e2[i].to,e2[i].fro,w );
    		}
    		q=read(); k=read(); s=read(); Dijkstra(); Kruskal();
    		while ( q-- )
    		{
    			int v=read(),p=read();
    			v=(v+k*lasans-1)%n+1; p=(p+k*lasans)%(s+1);
    			for ( int i=17; i>=0; i-- )
    				if ( fa[v][i] && a[fa[v][i]]>p ) v=fa[v][i];
    			printf( "%lld
    ",dis[v] ); lasans=dis[v];
    		}
    	}
    
    	fclose( stdin ); fclose( stdout );
    	return 0;
    }
    
  • 相关阅读:
    MySql常用命令
    多线程
    redhat7.7(centOS7)安装ORACLE 11g出坑教程及问题总结与解决
    使用 CGImageRef 出现的crash
    leetcode238. 除自身以外数组的乘积
    通过位运算实现求和
    leetcode求1+2+……+n
    leetcode101. 对称二叉树
    leetcode198. 打家劫舍
    leetcode394. 字符串解码
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/14105795.html
Copyright © 2011-2022 走看看