【闲话 No.2】 长链剖分
壱雫空
本来打算省选游记推这首歌的,但是省选破大防了,最后他来到了这里。感觉拿这个当起床铃效果一定很好(关键这歌超好听的!)。
もしこの雨が上がっても
忘れずに歩いてくよ
最初のひとしずくに 顔上げた今日の僕を
透明な傘で作る ひとり分だけの世界
(遮って隠れた)
そっと逃げ込んでいた
ビニール越しの空から
こぼれ落ちる音響いて
(弾けた合図)
滲む心へと溶けた
泣きじゃくっている この空といこう
(アスファルト染めていく 溢れ出したスコール)
通り過ぎる時を 待つだけじゃなくて
僕は見つめていたいんだよ
無色でもそこにあるもの
この雨が上がってく時
なにもなかったように
消えてく傘花みたいに心は
上手に折り畳めないから
過ぎ去ってしまう瞬間を
僕はあつめたいよ
ああ ひとしずくを
とめどなく傘にすべり落ちる
雫が揺れて描いてく(模様)
風に震えては
ぐずついてる僕みたいな
くすんでる今日を映した
迷い続けるこの空といこう
(曖昧に透ける色 眺めていたい)
ただよう雲だって 1秒先なんて
わからないままいくんだろう
不安で鈍く霞んでく 明日も
もしこの雨が上がっても
忘れたくないから
たった今を書きとめておきたいんだ
この手じゃ届かない
あの空から点線の糸で
つなぐように届いた ひとしずく
いまこの雨が上がってく
最後のひと粒が
小さく光って僕を映した
まだ道は乾かないだろう
潤んだ風を吸い込んだ
僕は連れていこう ああ ひとしずくを
就算这场大雨就此停止
我也将永不忘怀向前行
那抬头仰望最初一滴雨滴的今天的我
透明的伞创造出的一人份的世界
(遮蔽雨水隐藏身影)
悄悄逃入其中
从塑胶布另一头的天空
响起了雨珠落下的音色
(随着信号响起)
融入渗透了的内心之中
与哽咽啜泣的 这片天空同行吧
(雨水染遍了柏油路 强风随之满溢而出)
不光只是在等待 已逝去的时光
我是希望能够找到它啊
那无色却存在着的事物
当这场大雨渐渐止息时
仿佛一切都没发生过似地
逐渐消失的伞如花一般
正因无法好好折起内心
那些稍纵即逝的瞬间
多渴望能够收集它们
啊啊 那一滴雨珠
不断从伞上滑落而下
水滴摇曳绘制出的(模样)
在风中摇摆
那乌云密布的今日天空
映照出郁闷不安的内心
同这不断迷茫的天空为伴
(模糊又透明的色彩 我还想继续观望着)
那漂浮的云朵 也不知1秒后的未来
即便是如此但仍在前行
就算不安 使得明日朦胧暗淡
就算这场大雨就此停止
我也不愿忘记
只想记录并留下此刻
这只手触及不到的
从那片天空落下的点线
好似连接着 那一滴雨珠
此刻这大雨正逐渐停歇
那最后一颗小小的雨滴
微小地闪光并映射出我
前方道路仍尚未干透
轻轻吸入湿润的空气
我会将其带走 啊啊 那一滴雨珠
长链剖分
类似重链剖分,设 \(maxdep_i\) 表示节点 \(i\) 到所有叶子中经过节点数的最大值,那么长链剖分将每个节点 \(maxdep\) 最大的儿子(也就是最长链上的儿子)设为重儿子,将他们之间的边设为重边。
可以发现任意节点到根的轻边条数是 \(O(\sqrt{n})\) 的(以下 \(n\) 均与节点总数同阶)。考虑一条边数为 \(k\) 的链,链上全是轻边最少需要多少个点。我们规定根节点相对与叶子节点的方向为上方。考虑这条链最下方第 \(a+1\) 个点,这个点要使刚才的链上全为轻边就必须至少有一条与刚才的链无交且长至少为 \(O(a)\) 的链作为子树。将每个节点所需额外点数加起来共为 \(O(n^2)\),故当点数为 \(O(n)\) 时任意一点到根的轻边条数至少为 \(O(\sqrt{n})\)。
长链剖分求 k 级祖先
考虑我们事先通过倍增知道了每个节点的第 \(2^i\) 级祖先,现在我们要求 \(x\) 的 \(k\) 级祖先。我们先 \(O(1)\) 求出 \(a=\log_2{k}\),并将 \(x\) 跳到他的 \(2^a\) 级祖先处。考虑求新的节点的 \(k-2^a\) 级祖先。由于我们实现跳了 \(2^a\) 步,所以新的节点的 \(maxdep\) 一定大于等于 \(2^a\),也就是说他所在的长链长度大于等于 \(2^a\)。所以我们考虑将每条长链从下到上存储下来,如果这条长链长度为 \(b\),就在后面依次存储 \(b\) 个链顶的祖先。由于最后跳的步数一定小于 \(2^a\)(否则 \(a \neq {\log_2{k}}\)),所以可以直接在这个表上 \(O(1)\) 地找到 \(x\) 的 \(k\) 级祖先。最终我们用 \(O(n\log{n}+q)\)(以下 \(q\) 均与询问次数同阶)的时间解决了这个问题。
附上一份在 P5903 【模板】树上 K 级祖先 中跑得并不很快的代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define ui unsigned int
ui s;
inline ui get(ui x) {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return s = x;
}
struct node{
int end,nxt;
}edge[500100];
int n,q,root,cnt,head[500100],fa[22][500100],dep[500100],mxdp[500100],hson[500100],top[500100],pos[500100],lg[500100],ans;
vector<int> v[500100];
inline void add(int x,int y){
edge[++cnt]={y,head[x]};
head[x]=cnt;
}
void pre(int x,int y){
fa[0][x]=y;
dep[x]=dep[y]+1;
for(int i=1;i<20;++i) fa[i][x]=fa[i-1][fa[i-1][x]];
for(int i=head[x];i;i=edge[i].nxt){
pre(edge[i].end,x);
int tem=mxdp[edge[i].end]+1;
if(tem+1>mxdp[x]) mxdp[x]=tem,hson[x]=edge[i].end;
}
}
void dfs(int x,int y){
top[x]=y;
v[y].emplace_back(x);
if(hson[x]) dfs(hson[x],y);
for(int i=head[x];i;i=edge[i].nxt){
if(edge[i].end^hson[x])
dfs(edge[i].end,edge[i].end);
}
}
inline void query(int x,int y){
if(!y){ans=x;return;}
x=fa[lg[y]][x];
y-=(1<<lg[y]);
ans=v[top[x]][pos[x]+y];
}
int main(){
scanf("%d%d%u",&n,&q,&s);
for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
int ta,tb;
for(int i=1;i<=n;++i){
scanf("%d",&ta);
if(ta) add(ta,i);
else root=i;
}
pre(root,0);
dfs(root,root);
for(int i=1;i<=n;++i){
if(!v[i].empty()){
reverse(v[i].begin(),v[i].end());
int cur=0;
for(auto j:v[i]) pos[j]=cur++;
for(int j=v[i].size(),k=fa[0][i];j&&k;--j,k=fa[0][k]) v[i].emplace_back(k);
}
}
long long rtr=0;
for(int i=1;i<=q;++i){
ta=((get(s)^ans)%n)+1;
tb=((get(s)^ans)%dep[ta]+dep[ta])%dep[ta];
query(ta,tb);
rtr^=1ll*i*ans;
}
printf("%lld\n",rtr);
return 0;
}
长链剖分优化 dp
当我们在树上进行 dp 且有一维是 \(maxdep\) 是,可以考虑使用长链剖分进行优化。假设除了子树之间状态合并外所有操作复杂度均为 \(O(1)\)(包括直接继承状态和合并两个单个状态),子树间状态合并的复杂度与两颗子树中 \(maxdep\) 的最小值同阶,那么考虑每个节点直接继承重儿子的状态。然后以此合并各个轻儿子。可以发现所有合并的复杂度总和与每条重链的长度之和同阶(因为只有到达链顶使这条链在一条轻边下面才会发生一次复杂度与链长同阶的合并),所以总复杂度为 \(O(n)\)。
推图


浙公网安备 33010602011771号