Hotel加强版
POI2014
这次数据范围从5000改变到了100000,不能瞎搞了
前置技能:长链剖分
长链剖分有个小性质:所有长链的长度和为O(n)
这个很显然吧?~
题意
同Hotel,于是我复制了一遍
1.一棵n个节点的树
2.选三个点,令这三个点两两之间距离相同
3.问有多少种选点的方案
解
这次从动态规划角度考虑一下
1.定义
dp[x][j]:x为根,长度为j的链的个数
g[x][j]:x子树中,可以与x子树外,长度为j的链,组合成答案的点对数
特别注意g数组的定义,是x子树中能够与外部长度为j的链组合,并不是内部长度为j
2.状态转移
注意转移顺序(部分不能调换)
ans+=dp[x][j]*g[y][j+1]
↑从x中选一条长为j的链,对于子树y来说就是长为j+1的链了↑
ans+=g[x][j]*dp[y][j-1]
↑x中能与外界长为j的组合,对于子树y来说就是提供j-1的链↑
dp[x][j]+=dp[y][j-1]
↑y中的链到达x时,长度增加了1↑
g[x][j]+=g[y][j+1]
↑x中能与外界长为j的组合,到达儿子y就是长为j+1的链了↑
g[x][j]+=dp[x][j]*dp[y][j-1]
↑y提供长为j-1的链,到达x后,就是长为j的链了↑
3.观察一波
这里状态转移仅与dep有关
由于边的长度为1,链的长度与深度等价
4.长链剖分
定义:son[x]是x下面深度最大的儿子(姑且叫重儿子)
如果我们不把每个点的重儿子转移,即只转移所有的轻儿子,那么转移的复杂度就是O(n)的。
因为每条链只被转移一次,根据题头提到的性质,可以证明转移复杂度为O(n)
那么重儿子的问题怎么办呐?
通过指针转移,公用数组来省去转移的复杂度
先合并重儿子,并且让重儿子需要转移的内存与应该转移到位置的内存相同,这样就可以不用重新赋值了。
相当于计算一次,自动赋值完毕,但是这样重儿子必须优先访问
具体代码
#include<bits/stdc++.h>
using namespace std;
const int M=100005;
int n,dep[M],son[M];
long long space[M*10];
long long *dp[M],*g[M],*tot=space+M,ans;
int head[M],asdf;
struct edge {
int to,nxt;
} G[M*2];
void add_edge(int a,int b) {
G[++asdf].to=b;
G[asdf].nxt=head[a];
head[a]=asdf;
}
void newnode(int x) {
dp[x]=tot;
tot=tot+dep[x]*2+1;
g[x]=tot;
tot=tot+dep[x]*2+1;
}
void dfs1(int x,int f) {
son[x]=0;
for(int i=head[x]; i; i=G[i].nxt) {
int y=G[i].to;
if(y==f)continue;
dfs1(y,x);
if(dep[y]>dep[son[x]])son[x]=y;
}
dep[x]=dep[son[x]]+1;
}
void dfs2(int x,int f) {
dp[x][0]=1;
if(son[x]) {
dp[son[x]]=dp[x]+1;
//dp[x][j]+=dp[son[x]][j-1]
g[son[x]]=g[x]-1;
//g[x][j]+=g[son[x]][j+1]
dfs2(son[x],x);
ans+=g[son[x]][1];
}
for(int i=head[x]; i; i=G[i].nxt) {
int y=G[i].to;
if(y==f||y==son[x])continue;
newnode(y);
dfs2(y,x);
for(int j=dep[y]; j>=0; j--) {
ans+=dp[x][j]*g[y][j+1];
if(j>0) {
ans+=g[x][j]*dp[y][j-1];
g[x][j]+=dp[x][j]*dp[y][j-1];
dp[x][j]+=dp[y][j-1];
}
g[x][j]+=g[y][j+1];
}
}
}
int main() {
int a,b;
scanf("%d",&n);
for(int i=1; i<n; i++) {
scanf("%d %d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
dfs1(1,0);
newnode(1);
dfs2(1,0);
printf("%lld\n",ans);
return 0;
}