长链剖分学习笔记
长链剖分比起重链剖分,它更像dsu on tree那种的通过遍历顺序保存数据顺序来优化时间复杂度的小技巧,适用于统计链上信息问题的优化,还有就是\(O(1)\)求K级祖先;
一些长链剖分的概念
长儿子:某个节点所有儿子中所处链最长的那一个
链长:某点所处长链的长度
链顶:某条链深度最浅的那个节点
1.\(O(1)\)求K级祖先
首先需要知道一个东西:一个点的K级祖先所处的那条长链的链长一定大于等于K;这个是显然的,某点到那个祖先都已经有K那么长了,祖先所处的长链不可能比K小;然后需要预处理这些东西:所有点二的多少次方级的祖先,就是树上倍增那个;如果某条长链的链长为len,要预处理出链顶上面的len个祖先,以及对应那条链每个深度的节点,如下图;
红色的是一条长链,蓝色的是一条长链,蓝色的链长为\(3\),链顶是7
,它除了要预处理出倍增数组,还有预处理里出往上跳三个分别是什么,往下的链上的每个深度是什么;
最后还需要预处理出\(1\)到\(n\)每个数的最大的二进制位(\(HighBit\))是什么;
然后就可以\(O(1)\)求了:
比如我们要跳到\(u\)的\(K\)级祖先;
先往上跳到\(u\)的\(HighBit(K)\)祖先,记为\(v\),此时\(K-(1<<HighBit(K))\)肯定小于\(K\),而且\(v\)所在的那条长链的长度一定比\(1<<HighBit(K)\)长,所以说,\(v\)所在的那条链顶预处理的那些点中一定包含了\(u\)的\(K\)级祖先,这个时候再判断一下是往\(v\)所在长链的链顶向上跳还是向下跳就行了了;
#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
char c = getchar();
bool f = false;
for (x = 0; !isdigit(c); c = getchar()) {
if (c == '-') {
f = true;
}
}
for (; isdigit(c); c = getchar()) {
x = x * 10 + c - '0';
}
if (f) {
x = -x;
}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
read(x), read(y...);
}
const int N=5e5+10,mlog=19;
int n,p,Q;
int head[N],dep[N],son[N],len[N],top[N],Log[N],anc[N][mlog];
int dp[N<<2],*F[N],*G[N],*it=dp;
struct Edge {
int to,last;
Edge () {}
Edge (int a,int b) :to(a),last(b) {}
}edge[N<<1];
void ADD(int a,int b) {
edge[++p]=Edge(b,head[a]); head[a]=p;
edge[++p]=Edge(a,head[b]); head[b]=p;
}
void DFS(int u) {
for(int i=1;1<<i<dep[u];++i) anc[u][i]=anc[anc[u][i-1]][i-1];
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=anc[u][0]) {
anc[v][0]=u; dep[v]=dep[u]+1; DFS(v);
if(len[v]>len[son[u]]) son[u]=v;
}
}
len[u]=len[son[u]]+1;
}
void SFD(int u,int topv) {
top[u]=topv; F[u][0]=G[u][0]=u;
if(!son[u]) return;
F[son[u]]=F[u]+1; G[son[u]]=G[u]-1;
SFD(son[u],topv);
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(!top[v]) {
F[v]=it; it+=len[v]<<1; G[v]=it; it+=len[v]<<1;
for(int j=1;j<len[v];++j) {
if(j>dep[u]) break;
G[v][j]=G[u][j-1];
}
SFD(v,v);
}
}
}
int Get(int u,int k) {
if(!k) return u;
u=anc[u][Log[k]]; k^=1<<Log[k];
int t=dep[u]-dep[top[u]];
if(k<t) return F[top[u]][t-k];
return G[top[u]][k-t];
}
//#define rua
int main() {
// ios::sync_with_stdio(false);
#ifdef rua
freopen("GG.in","r",stdin);
freopen("M.out","w",stdout);
#endif
read(n);
for(int i=1;i<n;++i) {
int u,v; read(u,v);
ADD(u,v);
}
dep[1]=1; DFS(1);
F[1]=it; it+=len[1]<<1; G[1]=it; it+=len[1]<<1;
SFD(1,1);
Log[0]=-1;
for(int i=1;i<=n;++i) Log[i]=Log[i>>1]+1;
read(Q);
int lastANS=0;
while(Q--) {
int u,k; read(u,k);
u^=lastANS; k^=lastANS;
if(k>=dep[u]) printf("%d\n",lastANS=0);
else printf("%d\n",lastANS=Get(u,k));
}
return 0;
}
2.优化统计链上信息问题
处理这种问题一般就是先遍历长儿子,然后\(O(1)\)继承长儿子的贡献,然后暴力把短儿子的贡献加进去;每个点对应的长度信息,只会被暴力加入一次:当它所处的长链的链顶是某个点的短儿子的时候;所以一般的时间复杂度就是\(O(n)\)的,空间复杂度也是\(O(n)\)的,计算贡献的时候如果要套啥数据结构另算;具体实施起来的时候,一般会通过指针去\(O(1)\)继承,就比如第一道例题CF#1009F;
CF#1009F
题意就是让你统计每个节点的子树中所有点到该点距离相同的最多的距离,如果有数量相等的距离输出最短的那个,比较绕口,建议看一哈题面;
如果考虑暴力DP的话我们有这么一个转移式$$F[u][i]=F[v][i-1]$$
\(F[u][i]\)表示在\(u\)的子树中跟\(u\)的距离为\(i\)的点的个数,\(v\)是\(u\)的儿子;
这样的话每个点会去\(n\)次,总复杂度就是\(O(n^2)\),接受不了;考虑长链剖分,暴力加短儿子的那部分好说,就是个暴力DP,考虑怎么\(O(1)\)继承长儿子的贡献;考虑使用指针,每个点重儿子的\(F\)数组就等于这个点的\(F\)数组的指针\(+1\),正好就合适了:距当\(u\)节点为\(i\),那么距它儿子就是\(i-1\);然后这题就做完了;
所以长链剖分考虑优化的时候就去考虑先写出暴力DP,然后考虑怎么继承和怎么暴力转移;指针真的是个好东西;
#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
char c = getchar();
bool f = false;
for (x = 0; !isdigit(c); c = getchar()) {
if (c == '-') {
f = true;
}
}
for (; isdigit(c); c = getchar()) {
x = x * 10 + c - '0';
}
if (f) {
x = -x;
}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
read(x), read(y...);
}
const int N=1e6+10;
int n,p;
int head[N],len[N],son[N],dp[N],ANS[N];
int *F[N],*it=dp;
struct Edge {
int to,last;
Edge () {}
Edge (int a,int b) :to(a),last(b) {}
}edge[N<<1];
void ADD(int a,int b) {
edge[++p]=Edge(b,head[a]); head[a]=p;
edge[++p]=Edge(a,head[b]); head[b]=p;
}
void DFS(int u,int f) {
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=f) {
DFS(v,u);
if(len[v]>len[son[u]]) son[u]=v;
}
}
len[u]=len[son[u]]+1;
}
void DP(int u,int f) {
F[u][0]=1;
if(son[u]) {
F[son[u]]=F[u]+1;
DP(son[u],u);
ANS[u]=ANS[son[u]]+1;
}
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=f&&v!=son[u]) {
F[v]=it; it+=len[v];
DP(v,u);
for(int j=1;j<=len[v];++j) {
F[u][j]+=F[v][j-1];
if(F[u][j]>F[u][ANS[u]]||(F[u][j]==F[u][ANS[u]]&&j<ANS[u])) ANS[u]=j;
}
}
}
if(F[u][ANS[u]]==1) ANS[u]=0;
}
//#define rua
int main() {
// ios::sync_with_stdio(false);
#ifdef rua
freopen("GG.in","r",stdin);
#endif
read(n);
for(int i=1;i<n;++i) {
int u,v; read(u,v);
ADD(u,v);
}
DFS(1,0);
F[1]=it; it+=len[1];
DP(1,0);
for(int i=1;i<=n;++i) printf("%d\n",ANS[i]);
return 0;
}
BZOJ#4543
暴力DP然后长链剖分优化,还是用指针去\(O(1)\)继承;
#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
char c = getchar();
bool f = false;
for (x = 0; !isdigit(c); c = getchar()) {
if (c == '-') {
f = true;
}
}
for (; isdigit(c); c = getchar()) {
x = x * 10 + c - '0';
}
if (f) {
x = -x;
}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
read(x), read(y...);
}
const int N=1e5+10;
int n,p;
int head[N],son[N],len[N];
LL res;
LL dp[N<<2],*F[N],*G[N],*it=dp;
struct Edge {
int to,last;
Edge () {}
Edge (int a,int b) :to(a),last(b) {}
}edge[N<<1];
void ADD(int a,int b) {
edge[++p]=Edge(b,head[a]); head[a]=p;
edge[++p]=Edge(a,head[b]); head[b]=p;
}
void DFS(int u,int f) {
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=f) {
DFS(v,u);
if(len[v]>len[son[u]]) son[u]=v;
}
}
len[u]=len[son[u]]+1;
}
void DP(int u,int f) {
++F[u][0];
if(son[u]) {
F[son[u]]=F[u]+1; G[son[u]]=G[u]-1;
DP(son[u],u);
}
res+=G[u][0];
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=f&&v!=son[u]) {
F[v]=it; it+=len[v]<<1; G[v]=it; it+=len[v]<<1;
DP(v,u);
for(int j=0;j<len[v];++j) {
if(j) res+=F[u][j-1]*G[v][j];
res+=F[v][j]*G[u][j+1];
}
for(int j=0;j<len[v];++j) {
if(j) G[u][j-1]+=G[v][j];
G[u][j+1]+=F[u][j+1]*F[v][j];
F[u][j+1]+=F[v][j];
}
}
}
}
//#define rua
int main() {
// ios::sync_with_stdio(false);
#ifdef rua
freopen("GG.in","r",stdin);
#endif
read(n);
for(int i=1;i<n;++i) {
int u,v; read(u,v);
ADD(u,v);
}
DFS(1,0);
F[1]=it; it+=len[1]<<1; G[1]=it; it+=len[1]<<1;
DP(1,0);
printf("%lld\n",res);
return 0;
}
BZOJ#1758
分数规划的式子,先把式子化了之后发现是去找长度在\(L\)到\(U\)的链中权值最大的;转移的时候要查区间最大所以每条长链上维护一棵动态开点线段树;继承肯定一条长链用同一个根节点就行了,暴力转移的时候把短儿子的每个长度抠出来算贡献然后加进当前点的线段树里;复杂度\(O(nlog^2n)\),我常数有丶大,卡着过的;
#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
char c = getchar();
bool f = false;
for (x = 0; !isdigit(c); c = getchar()) {
if (c == '-') {
f = true;
}
}
for (; isdigit(c); c = getchar()) {
x = x * 10 + c - '0';
}
if (f) {
x = -x;
}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
read(x), read(y...);
}
const int N=1e5+10;
const double INF=2e18,eps=1e-6;
int n,p,A,B,size,CNT;
int head[N],son[N],len[N],top[N],root[N],dep[N];
double res,X;
double W[N],GG[N];
struct Edge {
int to,last;
double w;
Edge () {}
Edge (int a,int b,double c) :to(a),last(b),w(c) {}
}edge[N<<1];
void ADD(int a,int b,double c) {
edge[++p]=Edge(b,head[a],c); head[a]=p;
edge[++p]=Edge(a,head[b],c); head[b]=p;
}
void DFS(int u,int f) {
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=f) {
dep[v]=dep[u]+1; DFS(v,u);
if(len[v]>len[son[u]]) son[u]=v,W[u]=edge[i].w;
}
}
len[u]=len[son[u]]+1;
}
void SFD(int u,int topv) {
top[u]=topv;
if(!son[u]) return;
SFD(son[u],topv);
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(!top[v]) SFD(v,v);
}
}
struct Node {
int lo,ro;
double MAXV,lazy;
}Tr[N*20];
#define L Tr[o].lo
#define R Tr[o].ro
int New() {
int o=++size;
Tr[o].lazy=L=R=0; Tr[o].MAXV=-INF;
return o;
}
void Pushup(int o) {
Tr[o].MAXV=max(Tr[L].MAXV,Tr[R].MAXV);
}
void Down(int o,double t) {
if(!o) return;
Tr[o].MAXV+=t; Tr[o].lazy+=t;
}
void Pushdown(int o) {
if(Tr[o].lazy!=0) {
Down(L,Tr[o].lazy); Down(R,Tr[o].lazy);
Tr[o].lazy=0;
}
}
void Insert(int l,int r,int &o,int pos,double t) {
if(!o) o=New();
if(l==r) {
Tr[o].MAXV=max(Tr[o].MAXV,t);
return;
}
Pushdown(o);
int mid=l+r>>1;
if(pos<=mid) Insert(l,mid,L,pos,t);
else Insert(mid+1,r,R,pos,t);
Pushup(o);
}
void Modify(int l,int r,int o,int ql,int qr,double t) {
if(!o) return;
if(ql<=l&&r<=qr) return Down(o,t);
Pushdown(o);
int mid=l+r>>1;
if(ql<=mid) Modify(l,mid,L,ql,qr,t);
if(qr>mid) Modify(mid+1,r,R,ql,qr,t);
Pushup(o);
}
double Query(int l,int r,int o,int pos) {
if(l==r) return Tr[o].MAXV;
Pushdown(o);
int mid=l+r>>1;
double t;
if(pos<=mid) t=Query(l,mid,L,pos);
else t=Query(mid+1,r,R,pos);
Pushup(o);
return t;
}
double GetMax(int l,int r,int o,int ql,int qr) {
if(ql>r||!o) return -INF;
if(ql<=l&&r<=qr) return Tr[o].MAXV;
Pushdown(o);
int mid=l+r>>1;
double t=-INF;
if(ql<=mid) t=GetMax(l,mid,L,ql,qr);
if(qr>mid) t=max(t,GetMax(mid+1,r,R,ql,qr));
Pushup(o);
return t;
}
void Run(int l,int r,int o) {
if(!o) return;
if(l==r) {
GG[++CNT]=Tr[o].MAXV;
return;
}
Pushdown(o);
int mid=l+r>>1;
Run(l,mid,L); Run(mid+1,r,R);
}
void DP(int u,int f) {
if(u==top[u]) root[u]=0;
if(son[u]) DP(son[u],u);
int x=top[u];
Modify(dep[top[u]],dep[u]+len[u]-1,root[x],dep[u]+1,dep[u]+len[u]-1,W[u]+X);
Insert(dep[top[u]],dep[u]+len[u]-1,root[x],dep[u],0);
for(int i=head[u];i;i=edge[i].last) {
int v=edge[i].to;
if(v!=f&&v!=son[u]) {
DP(v,u);
CNT=0; Run(dep[v],dep[v]+len[v]-1,root[v]);
for(int j=1;j<=len[v];++j) if(j<=B) {
int l=max(0,A-j),r=B-j;
res=max(res,GG[j]+edge[i].w+X+GetMax(dep[top[u]],dep[u]+len[u]-1,root[x],dep[u]+l,dep[u]+r));
}
for(int j=1;j<=len[v];++j) Insert(dep[top[u]],dep[u]+len[u]-1,root[x],dep[u]+j,GG[j]+edge[i].w+X);
}
}
res=max(res,GetMax(dep[top[u]],dep[u]+len[u]-1,root[x],dep[u]+A,dep[u]+B));
}
//#define rua
int main() {
// ios::sync_with_stdio(false);
#ifdef rua
freopen("1.in","r",stdin);
#endif
read(n,A,B);
for(int i=1;i<n;++i) {
int u,v,w; read(u,v,w);
ADD(u,v,w);
}
dep[1]=1; DFS(1,0); SFD(1,1);
double l=0,r=1e6; Tr[0].MAXV=-INF;
while(r-l>1e-5) {
double mid=(l+r)/2;
size=0; res=-INF; X=-mid; DP(1,0);
if(res>=0) l=mid;
else r=mid;
}
printf("%.3lf\n",(l+r)/2);
return 0;
}