BZOJ4543[POI2014]Hotel加强版——长链剖分+树形DP
题意参见BZOJ3522
n<=100000
数据范围增强了,显然之前的转移方程不行了,那么不妨换一种。
因为不能枚举根来换根DP,那么我们描述的DP方程每个点要计算三个点都在这个点的子树内的方案数。
设f[i][j]表示i节点子树中与i距离为j的点的个数.
g[i][j]表示i节点子树中有g[i][j]对点满足每对点距离他们lca的距离都是d,他们lca距离i节点为d-j
也就是说现在已经找到两个节点了,需要再在没遍历的i的子树中找到一个距离i为j的点。
那么很容易得到转移方程:
ans+=f[x][j-1]*g[to[i]][j]
ans+=g[x][j+1]*f[to[i]][j]
g[x][j+1]+=f[to[i]][j]*f[x][j+1]
g[x][j-1]+=g[to[i]][j]
f[x][j+1]+=f[to[i]][j]
发现这个DP是合并深度信息转移的,而且子树信息被合并后就没用了,因此可以用长链剖分优化,每次继承重儿子信息,暴力合并其他子树。
因为父节点的f和g数组只由重儿子左移一位或右移一位得到,因此O(1)指针优化就好了。
具体应用到的长链剖分参见长链剖分
#include<queue> #include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int n; int x,y; int tot; ll s[1000010]; int head[100010]; int to[200010]; int mx[100010]; int next[200010]; int son[100010]; int fa[100010]; ll ans; ll *now=s+1; ll *f[100010]; ll *g[100010]; void add(int x,int y) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; } void dfs(int x) { mx[x]=0; for(int i=head[x];i;i=next[i]) { if(to[i]!=fa[x]) { fa[to[i]]=x; dfs(to[i]); mx[x]=max(mx[to[i]]+1,mx[x]); if(mx[to[i]]>mx[son[x]]) { son[x]=to[i]; } } } } void dfs2(int x) { if(son[x]) { f[son[x]]=f[x]+1; g[son[x]]=g[x]-1; dfs2(son[x]); } f[x][0]=1; ans+=g[x][0]; for(int i=head[x];i;i=next[i]) { if(to[i]!=fa[x]&&to[i]!=son[x]) { f[to[i]]=now; now+=mx[to[i]]+1; g[to[i]]=now+mx[to[i]]+1; now+=mx[to[i]]*2+2; dfs2(to[i]); for(int j=mx[to[i]];j>=0;j--) { if(j) { ans+=f[x][j-1]*g[to[i]][j]; } ans+=g[x][j+1]*f[to[i]][j]; g[x][j+1]+=f[to[i]][j]*f[x][j+1]; } for(int j=0;j<=mx[to[i]];j++) { if(j) { g[x][j-1]+=g[to[i]][j]; } f[x][j+1]+=f[to[i]][j]; } } } } int main() { scanf("%d",&n); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(1); f[1]=now; now+=mx[1]+1; g[1]=now+mx[1]+1; now+=mx[1]*2+2; dfs2(1); printf("%lld",ans); }