UOJ#53. UR #4追击圣诞老人 树链剖分 k短路
Posted zhouzhendong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,kleq 5 imes 10^5$
保证答案小于 $10^8$。
题解
由于 $k,n$ 同阶,所以后面的复杂度分析不区分 $n$ 和 $k$ 。
首先把每一个点可以到的节点拆成不超过两条链。
用树链剖分+线段树+预处理重链前缀min ,来实现 $O(log n)$ 求一条链上的权值最小点。
如果我们暴力的来,那么显然可以:
初始直接把所有点都扔到一个堆里面,以节点权值和为关键字,依次取出最小元素,每取出一个,就在堆中加入从这个点再走一步可以得到的方案,这样只需要取出 k 次就好了。
显然时空复杂度萎了。
于是我们考虑在树链剖分、线段树上打这些标记,这些标记的关键字是“从当前状态下,再向该区间中权值最小的点走得到的路径长度”,从堆中取出的时候分裂标记,就可以得到一个时间复杂度 $O(nlog^2 n)$ 空间复杂度 $O(nlog n)$ 的做法,仍然不足以通过此题。
考虑将标记的形式改为 $(a,b)$ ,即一条链上的两个端点。那么取出这条链的时候,只需要分两部分更新堆状态就好了:
1. 从这条链的最小权值点处分裂这条链,变成两条更短的链,加入堆中。
2. 从这条链的最小权值点出发,最多可以到达两条链,加入堆中。
于是我们就得到了一个空间复杂度 $O(n)$ ,时间复杂度 $O(nlog 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 ",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; }
以上是关于UOJ#53. UR #4追击圣诞老人 树链剖分 k短路的主要内容,如果未能解决你的问题,请参考以下文章
树链剖分UOJ150-Transportation Plan