日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

长链剖分

發布時間:2023/12/20 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 长链剖分 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

長鏈剖分也屬于樹鏈剖分的一種
一般講的樹剖都指輕重鏈剖分,它可以用于維護樹上路徑的信息
而長鏈剖分則是用于維護有關深度的信息


剖分方法

長鏈剖分的剖分方法與輕重鏈剖分極其相似
只需要把以子樹大小判斷重兒子改成以節點深度判斷即可

void dfs1(int u,int pa) {dep[u]=mxd[u]=dep[pa]+1;//mxd是該節點出發能到的最大深度for(int i=head[u];i;i=E[i].nxt){int v=E[i].v;if(v==pa) continue;dfs1(v,u);if(mxd[v]>mxd[u]) mxd[u]=mxd[v],son[u]=v; } }void dfs2(int u,int tp) {top[u]=tp;if(son[u]) dfs2(son[u],tp);for(int i=head[u];i;i=E[i].nxt){int v=E[i].v;if(v==son[u]||v==fa[u][0]) continue;dfs2(v,v);} }

長鏈剖分性質

性質1:長鏈剖分后,所有節點都僅屬于一條鏈
性質2:任意節點u的第k級祖先v所在鏈的長度一定大于k

證明:
若u,v在一條鏈上,那么顯然鏈長大于k
若u,v不在一條鏈上,假設v所在鏈長度小于k,那么u~v的鏈長顯然更長,u,v應在一條鏈上,矛盾

性質3:任意節點到達根節點經過的長鏈數是n\sqrt{n}n?級的

由性質2,每次跳躍到的新鏈長度不會小于當前鏈
最壞的情況,長鏈長度為1,2,3,…,n\sqrt{n}n?單調遞增


應用

樹上k級祖先

洛谷P5903 【模板】樹上 k 級祖先
比較經典的應用
用樹上倍增解決的話可以O(nlogn)預處理,O(logn)回答
而長鏈剖分可以O(nlogn)預處理,O(1)回答

先預處理出樹上倍增數組fa[u][i],表示u的第2^i級祖先
并預處理出 1 ~ n 每個數二進制下最高位的1的位置,即highbit(k),以下記hkh_khk?

長鏈剖分后,對于每條鏈,如果其長度為 len
那么在頂點處記錄頂點向上的len個祖先和向下的len個鏈上的兒子

對于每次u,k的詢問
先利用倍增數組將u跳到u的第2hk2^{h_k}2hk?級祖先,設剩下k′k'k級,顯然k′<2hkk'<2^{h_k}k<2hk?
而此時u所在長鏈長度>=2hk2^{h_k}2hk?>k’

所以再將u跳到鏈頂top[u]
之后剩下的級數直接在預處理出的向上向下的數組里O(1)查詢即可

#include<iostream> #include<cstdio> #include<cmath> #include<vector> #include<algorithm> #include<cstring> using namespace std; typedef long long lt; typedef unsigned int ui;int read() {int f=1,x=0;char ss=getchar();while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}return f*x; }const int maxn=500010; ui s; int n,Q,rt; struct node{int v,nxt;}E[maxn<<1]; int head[maxn],tot; int hi[maxn]; int fa[maxn][25],dep[maxn],mxd[maxn]; int son[maxn],top[maxn]; vector<int> pre[maxn],nxt[maxn]; lt ans[maxn*10];ui get(ui x) {x^=x<<13;x^=x>>17;x^=x<<5;return s=x; }void add(int u,int v) {E[++tot].nxt=head[u];E[tot].v=v;head[u]=tot; }void dfs1(int u,int pa) {dep[u]=mxd[u]=dep[pa]+1;for(int i=head[u];i;i=E[i].nxt){int v=E[i].v;if(v==pa) continue;dfs1(v,u);if(mxd[v]>mxd[u]) mxd[u]=mxd[v],son[u]=v; } }void dfs2(int u,int tp) {top[u]=tp;if(u==tp){int x=u;for(int i=0;i<=mxd[u]-dep[u];++i){pre[u].push_back(x);x=fa[x][0];}x=u;for(int i=0;i<=mxd[u]-dep[u];++i){nxt[u].push_back(x);x=son[x];}}if(son[u]) dfs2(son[u],tp);for(int i=head[u];i;i=E[i].nxt){int v=E[i].v;if(v==son[u]||v==fa[u][0]) continue;dfs2(v,v);} }int solve(int u,int k) {if(k==0) return u;u=fa[u][hi[k]];k-=1<<hi[k];k-=dep[u]-dep[top[u]];u=top[u];return k>=0?pre[u][k]:nxt[u][-k]; }int main() {n=read(); Q=read(); s=read();hi[0]=-1;for(int i=1;i<=n;++i){fa[i][0]=read();add(fa[i][0],i); add(i,fa[i][0]);if(fa[i][0]==0) rt=i;hi[i]=hi[i>>1]+1;}dfs1(rt,0); dfs2(rt,rt);for(int i=1;(1<<i)<=n;++i)for(int u=1;u<=n;++u)fa[u][i]=fa[fa[u][i-1]][i-1];for(int i=1;i<=Q;++i){int x=(get(s)^ans[i-1])%n+1;int k=(get(s)^ans[i-1])%dep[x];ans[i]=solve(x,k);}lt res=ans[1];for(int i=2;i<=Q;++i)res^=(lt)i*ans[i];printf("%lld",res);return 0; }

與深度有關的DP

CodeForces - 1009F Dominant Indices

題目大意: 給定一棵以1為根,n個節點的樹。設 d(u,x)為u子樹中到u距離為x的節點數。
對于每個點,求使得d(u,x)最大的x,若有多個則輸出最小的x

題目分析:
設dp[u][k]表示u子樹中與u距離為k的節點數,
則 dp[u][k]=Σdp[v][k-1] (v為u的兒子)

設mxlen[u]表示u到最遠的葉子結點的距離
每次轉移時,可以先選擇任意一個兒子v,把dp[v][]直接錯位復制給dp[u][]
可通過數組的指針操作( dp[u]=dp[v]+1 )以O(1)實現

之后其他兒子的信息暴力合并到dp[u]
即對每個兒子v,按照上述轉移方程進行mxlen[v]次更新

可以發現這樣做的復雜度直接由每次暴力合并的mxlen決定
如果我們把第一次直接復制的兒子選為長鏈剖分的重兒子
那么暴力更新的mxlen就取得最小值,總復雜度就變成了O(n)

還有一點要注意的是dp數組顯然不能開dp[1e6][1e6]那么大
但由于所有長鏈長度和就是n,所以可以用指針指向一個大小為n的數組動態使用空間

#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std;int read() {int f=1,x=0;char ss=getchar();while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}return f*x; }const int maxn=1000010; int n; struct node{int v,nxt;}E[maxn<<1]; int head[maxn],tot; int son[maxn],mxlen[maxn]; int buf[maxn]; int *dp[maxn],*cur=buf; int ans[maxn];void add(int u,int v) {E[++tot].nxt=head[u];E[tot].v=v;head[u]=tot; }void dfs1(int u,int pa) {for(int i=head[u];i;i=E[i].nxt){int v=E[i].v;if(v==pa) continue;dfs1(v,u);if(mxlen[v]>mxlen[son[u]]) son[u]=v;}mxlen[u]=mxlen[son[u]]+1; }void dfs2(int u,int pa) {dp[u][0]=1;if(son[u]){dp[son[u]]=dp[u]+1;//錯位更新信息dfs2(son[u],u);ans[u]=ans[son[u]]+1;}for(int i=head[u];i;i=E[i].nxt){int v=E[i].v;if(v==son[u]||v==pa) continue;dp[v]=cur; cur+=mxlen[v];dfs2(v,u);for(int j=1;j<=mxlen[v];++j)//暴力更新{dp[u][j]+=dp[v][j-1];if(dp[u][j]>dp[u][ans[u]]) ans[u]=j;else if(dp[u][j]==dp[u][ans[u]] && j<ans[u]) ans[u]=j;}}if(dp[u][ans[u]]==1) ans[u]=0; }int main() {n=read();for(int i=1;i<n;++i){int u=read(),v=read();add(u,v); add(v,u);}dfs1(1,0); dp[1]=cur; cur+=mxlen[1];dfs2(1,0);for(int i=1;i<=n;++i)printf("%d\n",ans[i]);return 0; }

總結

以上是生活随笔為你收集整理的长链剖分的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。