树链剖分 树形DP
%%%neither_nor大佬
BZOJ4543
BZOJ3522(没有权限的同学可以在洛谷上看题目)
洛谷
一道神奇的题目。
BZOJ3522/洛谷P3565
考虑树形DP。定义
f[i][j]
表示
i
子树中深度为
转移(应该看看都能懂):
//v是目前边指向的点
g[x][j-1]+=g[v][j];
g[x][j+1]+=f[x][j+1]*f[v][j];
f[x][j+1]+=f[v][j];
ans+=f[x][j-1]*g[v][j];
//实际实现的顺序会不一样
BZOJ4543
在 n =1e5的条件下,无论是空间还是时间都会炸掉,因此我们考虑进行优化。
接下来就是神奇的时刻。
对于每个点的第一个儿子,
而对于其它点,转移是 O(dep[x]) 的。所以我们不妨每次把最长的那个儿子进行 O(1) 转移。而这个就用到了长链剖分。
可以证明并不会证明复杂度为
O(n)
代码:
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
using namespace std;
typedef long long LL;
struct edge{
int next,to;
}ed[N*2];
int n,k,h[N],fa[N],to[N],sz[N];
LL *f[N],*g[N],myy[N*5],*now=myy+1,ans;
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int x=0; char ch=readc();
while (!isdigit(ch)) ch=readc();
while (isdigit(ch)) x=x*10+ch-48,ch=readc();
return x;
}
inline void addedge(int x,int y){
ed[++k].next=h[x],ed[k].to=y,h[x]=k;
}
void dfs1(int x){
for (int i=h[x];i;i=ed[i].next)
if (ed[i].to!=fa[x]){
int v=ed[i].to; fa[v]=x,dfs1(v);
sz[x]=max(sz[v]+1,sz[x]);
if (sz[to[x]]<sz[v]) to[x]=v;
}
}
inline void chng(int x){
f[x]=now,now+=sz[x]+1,g[x]=now+sz[x]+1,now+=sz[x]*2+2;
//每次给转移数组开(重链深度)的空间
}
void dfs2(int x){
if (to[x])
f[to[x]]=f[x]+1,g[to[x]]=g[x]-1,dfs2(to[x]);
f[x][0]=1,ans+=g[x][0];
for (int i=h[x];i;i=ed[i].next)
if (ed[i].to!=fa[x]&&ed[i].to!=to[x]){
int v=ed[i].to; chng(v),dfs2(v);
for (int j=sz[v];~j;j--){
if (j) ans+=f[x][j-1]*g[v][j];
ans+=g[x][j+1]*f[v][j];
g[x][j+1]+=f[x][j+1]*f[v][j];
}
for (int j=0;j<=sz[v];j++){
if (j) g[x][j-1]+=g[v][j];
f[x][j+1]+=f[v][j];
}
}
}
int main(){
n=_read(),sz[0]=-1;
for (int i=1;i<n;i++){
int u=_read(),v=_read();
addedge(u,v),addedge(v,u);
}
dfs1(1),chng(1),dfs2(1);
return printf("%lld\n",ans),0;
}