这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
|
2020-2021:teams:legal_string:jxm2001:长链剖分 [2020/05/29 20:44] jxm2001 |
2020-2021:teams:legal_string:jxm2001:长链剖分 [2020/07/27 22:54] (当前版本) jxm2001 ↷ 页面2020-2021:teams:legal_string:长链剖分被移动至2020-2021:teams:legal_string:jxm2001:长链剖分 |
||
|---|---|---|---|
| 行 5: | 行 5: | ||
| 一种可以在线性时间复杂度维护子树中**仅限深度有关的信息**的算法,主要用于一些特殊的 $\text{dp}$ | 一种可以在线性时间复杂度维护子树中**仅限深度有关的信息**的算法,主要用于一些特殊的 $\text{dp}$ | ||
| - | ===== 算法思想 ==== | + | ===== 算法思想 ===== |
| 类似重链剖分,将儿子分为重儿子和轻儿子,重儿子组成的链构成长链 | 类似重链剖分,将儿子分为重儿子和轻儿子,重儿子组成的链构成长链 | ||
| 行 11: | 行 11: | ||
| 但不同的是重链剖分重儿子是子树结点最多的结点,长链剖分长儿子是子树深度最大的结点 | 但不同的是重链剖分重儿子是子树结点最多的结点,长链剖分长儿子是子树深度最大的结点 | ||
| - | 长链剖分的一个重要性质是一个结点 $x$ 的 $k$ 级祖先所在的长链长度一定 $\ge k$ | + | 长链剖分有两个重要性质 |
| - | 考虑结点 $x$ 和它的 $k$ 级祖先 $y$,如果 $x$ 和 $y$ 属于同一条长链,上述性质成立 | + | 性质一:所有长链长度和为 $n$ |
| - | 如果 $x$ 和 $y$ 不属于同一条长链,知 $y$ 的重儿子子树的深度一定大于 $x$ 的深度,所以上述性质也成立 | + | 显然所有点均属于且仅属于一条长链,所以性质一成立 |
| + | |||
| + | 性质二:长链剖分的一个重要性质是一个结点 $x$ 的 $k$ 级祖先所在的长链长度一定 $\ge k$ | ||
| + | |||
| + | 考虑结点 $x$ 和它的 $k$ 级祖先 $y$,如果 $x$ 和 $y$ 属于同一条长链,该情况下性质二成立 | ||
| + | |||
| + | 如果 $x$ 和 $y$ 不属于同一条长链,知 $y$ 的重儿子子树的深度一定大于 $x$ 的深度,该情况下性质二也成立 | ||
| ===== 算法应用 ===== | ===== 算法应用 ===== | ||
| 行 43: | 行 49: | ||
| <hidden 查看代码> | <hidden 查看代码> | ||
| <code cpp> | <code cpp> | ||
| - | #include <iostream> | ||
| - | #include <cstdio> | ||
| - | #include <algorithm> | ||
| - | #include <cstring> | ||
| - | #include <cctype> | ||
| - | #include <vector> | ||
| - | #define _for(i,a,b) for(int i=(a);i<(b);++i) | ||
| - | #define _rep(i,a,b) for(int i=(a);i<=(b);++i) | ||
| - | using namespace std; | ||
| - | typedef long long LL; | ||
| - | inline int read_int(){ | ||
| - | int t=0;bool sign=false;char c=getchar(); | ||
| - | while(!isdigit(c)){sign|=c=='-';c=getchar();} | ||
| - | while(isdigit(c)){t=(t<<1)+(t<<3)+(c&15);c=getchar();} | ||
| - | return sign?-t:t; | ||
| - | } | ||
| - | inline void write(LL x){ | ||
| - | register char c[21],len=0; | ||
| - | if(!x)return putchar('0'),void(); | ||
| - | if(x<0)x=-x,putchar('-'); | ||
| - | while(x)c[++len]=x%10,x/=10; | ||
| - | while(len)putchar(c[len--]+48); | ||
| - | } | ||
| - | inline void space(LL x){write(x),putchar(' ');} | ||
| - | inline void enter(LL x){write(x),putchar('\n');} | ||
| const int MAXN=5e5+5,MAXM=21; | const int MAXN=5e5+5,MAXM=21; | ||
| unsigned int s; | unsigned int s; | ||
| 行 162: | 行 143: | ||
| 给定一棵 $n$ 个结点的树,问有多少个无序点对 $(i,j,k)$ 满足 $i,j,k$ 两两间距离相等 | 给定一棵 $n$ 个结点的树,问有多少个无序点对 $(i,j,k)$ 满足 $i,j,k$ 两两间距离相等 | ||
| + | |||
| + | 树形$\text{dp}$,状态转移方程可以参考这份博客[[https://www.luogu.com.cn/blog/xht37/solution-p3565|详情]] | ||
| + | |||
| + | 利用指针位移使得父结点以 $O(1)$ 时间继承重儿子信息,暴力继承轻儿子信息 | ||
| + | |||
| + | 设当前结点为 $u$,$u$ 的儿子结点为 $x$,每次暴力继承时间复杂度为 $O\left(\text{len}\left(x\right)\right)$ | ||
| + | |||
| + | 总时间复杂度为 $\sum_u\left(\left(\sum_x \text{len}\left(x\right)\right)-\text{len}\left(u\right)+1\right)$ | ||
| + | |||
| + | 每个结点仅在 $\sum_u\left(\left(\sum_x \text{len}\left(x\right)\right)\right)$ 作为它的父结点的子结点出现一次 | ||
| + | |||
| + | 所以有 $\sum_u\left(\left(\sum_x \text{len}\left(x\right)\right)\right)=\sum_u \text{len}\left(u\right)-\text{len}(root)$ | ||
| + | |||
| + | 所以总时间复杂度为 $O\left(n\right)$ | ||
| + | |||
| + | <hidden 查看代码> | ||
| + | <code cpp> | ||
| + | const int MAXN=5e5+5; | ||
| + | struct Edge{ | ||
| + | int to,next; | ||
| + | }edge[MAXN<<1]; | ||
| + | int head[MAXN],m; | ||
| + | void Insert(int u,int v){ | ||
| + | edge[++m].to=v; | ||
| + | edge[m].next=head[u]; | ||
| + | head[u]=m; | ||
| + | } | ||
| + | int d[MAXN],Son[MAXN],len[MAXN]; | ||
| + | LL *f[MAXN],*g[MAXN],dp[MAXN<<2],*pos=dp,ans; | ||
| + | void Malloc(int u){ | ||
| + | f[u]=pos,pos+=len[u]<<1; | ||
| + | g[u]=pos,pos+=len[u]<<1; | ||
| + | } | ||
| + | void dfs_1(int u,int depth,int fa){ | ||
| + | d[u]=depth;Son[u]=0; | ||
| + | for(int i=head[u];i;i=edge[i].next){ | ||
| + | int v=edge[i].to; | ||
| + | if(v==fa) | ||
| + | continue; | ||
| + | dfs_1(v,depth+1,u); | ||
| + | if(len[v]>len[Son[u]]) | ||
| + | Son[u]=v; | ||
| + | } | ||
| + | len[u]=len[Son[u]]+1; | ||
| + | } | ||
| + | void dfs_2(int u,int fa){ | ||
| + | if(Son[u]){ | ||
| + | f[Son[u]]=f[u]+1,g[Son[u]]=g[u]-1; | ||
| + | dfs_2(Son[u],u); | ||
| + | } | ||
| + | f[u][0]=1,ans+=g[u][0]; | ||
| + | for(int i=head[u];i;i=edge[i].next){ | ||
| + | int v=edge[i].to; | ||
| + | if(v==fa||v==Son[u]) | ||
| + | continue; | ||
| + | Malloc(v); | ||
| + | dfs_2(v,u); | ||
| + | _for(i,0,len[v]){ | ||
| + | if(i) ans+=f[u][i-1]*g[v][i]; | ||
| + | ans+=g[u][i+1]*f[v][i]; | ||
| + | } | ||
| + | _for(i,0,len[v]){ | ||
| + | g[u][i+1]+=f[u][i+1]*f[v][i]; | ||
| + | if(i) g[u][i-1]+=g[v][i]; | ||
| + | f[u][i+1]+=f[v][i]; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | int main() | ||
| + | { | ||
| + | int n=read_int(),u,v,root=1; | ||
| + | _for(i,1,n){ | ||
| + | u=read_int();v=read_int(); | ||
| + | Insert(u,v); | ||
| + | Insert(v,u); | ||
| + | } | ||
| + | dfs_1(root,0,0); | ||
| + | Malloc(root); | ||
| + | dfs_2(root,0); | ||
| + | enter(ans); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | </hidden> | ||