UOJ#53. 【UR #4】追击圣诞老人 树链剖分 k短路

原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ53.html

题意

  给定一棵有 n 个节点的树。

  每一个点有一个权值。

  对于每一个 $i$ 给定三个参数 $a_i,b_i,c_i$ ,从第 $i$ 个点出发下一步能到达的点 x 需要满足以下三个要求之一:

  1. x 在 $a_i$ 到 $b_i$ 的简单路径上。

  2. x 在 $a_i$ 到 $c_i$ 的简单路径上。

  3. x 在 $c_i$ 到 $b_i$ 的简单路径上。

  问从任意一个点出发,经过的节点的权值和前 k 小的路线长度为多少。注意可以重复经过节点。

  空间限制 100MB,时间限制 3s

  $n,k\leq 5\times 10^5$

  保证答案小于 $10^8$。

题解

  由于 $k,n$ 同阶,所以后面的复杂度分析不区分 $n$ 和 $k$ 。

  首先把每一个点可以到的节点拆成不超过两条链。

  用树链剖分+线段树+预处理重链前缀min ,来实现 $O(\log n)$ 求一条链上的权值最小点。

  如果我们暴力的来,那么显然可以:

    初始直接把所有点都扔到一个堆里面,以节点权值和为关键字,依次取出最小元素,每取出一个,就在堆中加入从这个点再走一步可以得到的方案,这样只需要取出 k 次就好了。

  显然时空复杂度萎了。

  于是我们考虑在树链剖分、线段树上打这些标记,这些标记的关键字是“从当前状态下,再向该区间中权值最小的点走得到的路径长度”,从堆中取出的时候分裂标记,就可以得到一个时间复杂度 $O(n\log^2 n)$ 空间复杂度 $O(n\log n)$ 的做法,仍然不足以通过此题。

  考虑将标记的形式改为 $(a,b)$ ,即一条链上的两个端点。那么取出这条链的时候,只需要分两部分更新堆状态就好了:

  1. 从这条链的最小权值点处分裂这条链,变成两条更短的链,加入堆中。

  2. 从这条链的最小权值点出发,最多可以到达两条链,加入堆中。

  于是我们就得到了一个空间复杂度 $O(n)$ ,时间复杂度 $O(n\log n)$ 的做法。

  

  然后由于博主人傻常数大,还是被卡空间了。

  于是强行把vector数组写成数组模拟链表,把系统堆写成手写堆……

  最后突然发现他保证答案小于 1e8 ,不是边权小于 1e8 的时候我惊呆了。

  之前卡了这么多常数,其实只需要把存路径长度的数据类型从 longlong 改成 int 就好了……

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read(){
	LL x=0,f=0;
	char ch=getchar();
	while (!isdigit(ch))
		f|=ch==‘-‘,ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=500005;
int n,k;
struct Info{
	int a,b,c,d;
}v[N];
struct Gragh{
	static const int M=N*2;
	int cnt,y[M],nxt[M],fst[N];
	void clear(){
		cnt=1;
		memset(fst,0,sizeof fst);
	}
	void add(int a,int b){
		y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt;
	}
}g;
int w[N];
int fa[N],depth[N],size[N],son[N],top[N],I[N],O[N],aI[N],Min[N],Time=0;
void dfs1(int x,int pre,int d){
	depth[x]=d,fa[x]=pre,son[x]=0,size[x]=1;
	for (int i=g.fst[x];i;i=g.nxt[i]){
		int y=g.y[i];
		if (y!=pre){
			dfs1(y,x,d+1);
			size[x]+=size[y];
			if (!son[x]||size[y]>size[son[x]])
				son[x]=y;
		}
	}
}
void ckwMin(int &x,int y){
	if (w[x]>w[y])
		x=y;
}
void dfs2(int x,int Top){
	top[x]=Top,aI[I[x]=++Time]=x;
	if (son[x])
		ckwMin(Min[son[x]],Min[x]),dfs2(son[x],Top);
	for (int i=g.fst[x];i;i=g.nxt[i]){
		int y=g.y[i];
		if (y!=fa[x]&&y!=son[x])
			dfs2(y,y);
	}
	O[x]=Time;
}
int LCA(int x,int y){
	int fx=top[x],fy=top[y];
	while (fx!=fy){
		if (depth[fx]<depth[fy])
			swap(fx,fy),swap(x,y);
		x=fa[fx],fx=top[x];
	}
	return depth[x]<depth[y]?x:y;
}
int isanc(int x,int y){
	return I[x]<=I[y]&&I[y]<=O[x];
}
int go_up(int x,int y){
	if (isanc(aI[I[y]+1],x))
		return aI[I[y]+1];
	int fx=top[x];
	while (depth[fx]>depth[y]){
		x=fx;
		if (depth[fa[x]]>depth[y])
			x=fa[x],fx=top[x];
		else
			break;
	}
	return x;
}
namespace Seg{
	int v[N<<2];
	void build(int rt,int L,int R){
		if (L==R)
			return (void)(v[rt]=aI[L]);
		int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
		build(ls,L,mid);
		build(rs,mid+1,R);
		ckwMin(v[rt]=v[ls],v[rs]);
	}
	int query(int rt,int L,int R,int xL,int xR){
		if (xL<=L&&R<=xR)
			return v[rt];
		int mid=(L+R)>>1,ls=rt<<1,rs=ls|1;
		if (xR<=mid)
			return query(ls,L,mid,xL,xR);
		if (xL>mid)
			return query(rs,mid+1,R,xL,xR);
		int res=query(ls,L,mid,xL,xR);
		ckwMin(res,query(rs,mid+1,R,xL,xR));
		return res;
	}
	int query(int x,int y){
		int ans=x,fx=top[x],fy=top[y];
		while (fx!=fy){
			if (depth[fx]<depth[fy])
				swap(fx,fy),swap(x,y);
			ckwMin(ans,Min[x]);
			x=fa[fx],fx=top[x];
		}
		if (depth[x]>depth[y])
			swap(x,y);
		ckwMin(ans,query(1,1,n,I[x],I[y]));
		return ans;
	}
}
const int Size=N*5;
struct Node{
	int a,b,len;
	Node(){}
	Node(int _a,int _b,int pre){
		a=_a,b=_b;
		len=pre+w[Seg :: query(a,b)];
	}
}s[Size];
int stt=0;
bool cmp(int a,int b){
	return s[a].len>s[b].len;
}
struct priority_Queue{
	int v[Size],s;
	bool empty(){
		return s==0;
	}
	void up(int x){
		int y=x>>1;
		while (y){
			if (cmp(v[y],v[x]))
				swap(v[x],v[y]),x=y,y=x>>1;
			else
				break;
		}
	}
	void down(int x){
		int y=x<<1;
		while (y<=s){
			if (y<s&&cmp(v[y],v[y+1]))
				y|=1;
			if (cmp(v[x],v[y]))
				swap(v[x],v[y]),x=y,y=x<<1;
			else
				break;
		}
	}
	void pop(){
		swap(v[1],v[s--]);
		down(1);
	}
	int top(){
		return v[1];
	}
	void push(int x){
		v[++s]=x;
		up(s);
	}
}Q;
void push(Node x){
	s[++stt]=x,Q.push(stt);
}
void Split(Node x,int Mi){
	int a=x.a,b=x.b,m=Mi;
	if (!isanc(m,a))
		swap(a,b);
	if (m!=a){
		int aa=go_up(a,m);
		push(Node(a,aa,x.len-w[m]));
	}
	if (m!=b){
		int bb;
		if (isanc(m,b))
			bb=go_up(b,m);
		else
			bb=fa[m];
		push(Node(b,bb,x.len-w[m]));
	}
}
void solve(){
	while (!Q.empty())
		Q.pop();
	for (int i=1;i<=n;i++)
		push(Node(i,i,0));
	while (k--){
		Node now=s[Q.top()];
		Q.pop();
		printf("%d\n",now.len);
		int x=Seg :: query(now.a,now.b);
		Split(now,x);
		if (~v[x].a)
			push(Node(v[x].a,v[x].b,now.len));
		if (~v[x].c)
			push(Node(v[x].c,v[x].d,now.len));
	}
}
int main(){
	n=read(),k=read();
	for (int i=1;i<=n;i++)
		w[i]=read(),Min[i]=i;
	g.clear();
	for (int i=2;i<=n;i++){
		int a=read();
		g.add(a,i);
		g.add(i,a);
	}
	for (int i=1;i<=n;i++)
		v[i].a=read(),v[i].b=read(),v[i].c=read();
	dfs1(1,0,0);
	dfs2(1,1);
	for (int i=1;i<=n;i++){
		int a=v[i].a,b=v[i].b,c=v[i].c;
		v[i].a=v[i].b=v[i].c=v[i].d=-1;
		if (depth[c]<depth[b])
			swap(b,c);
		if (depth[b]<depth[a])
			swap(a,b);
		if (depth[c]<depth[b])
			swap(b,c);
		if (a==b||b==c){
			v[i].a=a,v[i].b=c;
			continue;
		}
		int ab=LCA(a,b),ac=LCA(a,c),bc=LCA(b,c);
		if (ab==ac&&ab==bc){
			int g=ab;
			if (depth[a]>depth[g]){
				v[i].a=a,v[i].b=b;
				v[i].c=c,v[i].d=go_up(c,g);
				continue;
			}
			v[i].a=b,v[i].b=c;
			continue;
		}
		if (ab==bc)
			swap(a,b),swap(ac,bc);
		else if (ac==bc)
			swap(a,c),swap(ab,bc);
		if (depth[c]<depth[b])
			swap(b,c),swap(ab,ac);
		if (isanc(b,c)){
			v[i].a=a,v[i].b=c;
			continue;
		}
		v[i].a=a,v[i].b=b;
		int d=go_up(c,bc);
		v[i].c=c,v[i].d=d;
	}
	Seg :: build(1,1,n);
	solve();
	return 0;
}

  

原文地址:https://www.cnblogs.com/zhouzhendong/p/UOJ53.html

时间: 2024-12-31 18:16:29

UOJ#53. 【UR #4】追击圣诞老人 树链剖分 k短路的相关文章

BZOJ 4326 树链剖分+二分+差分+记忆化

去年NOIP的时候我还不会树链剖分! 还是被UOJ 的数据卡了一组. 差分的思想还是很神啊! 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <ctime> 6 #include <cstdlib> 7 using namespace std; 8 const int Maxn=300100

[Bzoj4196] [NOI2015] 软件包管理器 [树链剖分,线段树]

题解摘要:树链剖分后用线段树区间查询修改,对于安装软件,将改点到根的路径全部变为1,对于卸载软件,将子树清空.注意边界,编号是从0开始的,容易漏掉树根. 第一次写树剖- 1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cstring> 6 #include <cmath> 7 #inclu

树链剖分简(单)介(绍)

树链剖分可以算是一种数据结构(一大堆数组,按照这个意思,主席树就是一大堆线段树).将一棵树分割成许多条连续的树链,方便完成一下问题: 单点修改(dfs序可以完成) 求LCA(各种乱搞也可以) 树链修改(修改任意树上两点之间的唯一路径) 树链查询 (各种操作)  前两个内容可以用其他方式解决,但是下面两种操作倍增.st表,dfs序就很难解决(解决当然可以解决,只是耗时长点而已).下面开始步入正题. 树链剖分的主要目的是分割树,使它成一条链,然后交给其他数据结构(如线段树,Splay)来进行维护.常

bzoj1146整体二分+树链剖分+树状数组

其实也没啥好说的 用树状数组可以O(logn)的查询 套一层整体二分就可以做到O(nlngn) 最后用树链剖分让序列上树 1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 inline int read() 7 { 8 int x=0,f=1,ch=getchar(); 9 while(ch<

BZOJ 1036 [ZJOI2008]树的统计Count (树链剖分 - 点权剖分 - 单点权修改)

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1036 树链剖分模版题,打的时候注意点就行.做这题的时候,真的傻了,单词拼错检查了一个多小时... 代码如下: 1 //树链剖分 点权修改 修改单节点 2 #include <iostream> 3 #include <cstring> 4 #include <algorithm> 5 #include <cstdio> 6 using namespa

poj 3237 Tree(树链剖分,线段树)

Tree Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 7268   Accepted: 1969 Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbered 1 through N − 1. Each edge is associated with

Hdu 3966 Aragorn&#39;s Story (树链剖分 + 线段树区间更新)

题目链接: Hdu 3966 Aragorn's Story 题目描述: 给出一个树,每个节点都有一个权值,有三种操作: 1:( I, i, j, x ) 从i到j的路径上经过的节点全部都加上x: 2:( D, i, j, x ) 从i到j的路径上经过的节点全部都减去x: 3:(Q, x) 查询节点x的权值为多少? 解题思路: 可以用树链剖分对节点进行hash,然后用线段树维护(修改,查询),数据范围比较大,要对线段树进行区间更新 1 #include <cstdio> 2 #include

树链剖分专题

学习了一下树链剖分,找了几个有意义的题目训练一下 前4题是基础训练, A.B是AOV树(点记录信息) C.D是AOE树(边记录信息) *注意一下poj好像是提交的代码包含/**/这样的注释会出问题 A.HDU 3966 题意:给你N个点M条边的一棵AOV树,有P次操作 操作分别是:‘I’:将C1到C2路径上的点增加K:     ‘D’:将C1到C2路径上的点减少K:     ‘Q’:问你C点上的权: 解:树链剖分基础题,需要注意的是杭电的服务器是windows的,代码中加入用下面这句话扩栈 #p

树链剖分 模板

关于树链剖分 模板: 1 const int MAXN = 20010; 2 struct Edge 3 { 4 int to,next; 5 } edge[MAXN*2]; 6 int head[MAXN],tot; 7 8 int top[MAXN];//top[v]表示v所在的重链的顶端节点 9 int fa[MAXN]; //父亲节点 10 int deep[MAXN];//深度 11 int num[MAXN];//num[v]表示以v为根的子树的节点数 12 int p[MAXN];