原文链接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;
}