题解 P3565 【[POI2014]HOT-Hotels】
P3565 [POI2014]HOT-Hotels
参考题解
题目大意: 给定一棵树,在树上选 3 个点,要求两两距离相等,求方案数。
三个点树上两两距离为d存在下面两种情况
- 某个点三个子树(保证该点是LCA)中分别由三个点距离它为d
- 对于某一个点,它的 d 级祖先以及子树内两个以它为LCA,距它 d 的点
对于情况一,设计dp:
对于情况二,设计dp:
对于
而对于
- 某个子树内部存在两个点:
g_{u,j}=\sum_{v\in son_u}g_{v,j+1} - 两个不同的子树各贡献一个点,以
u 为LCA:g_{u,j}=\sum_{v,w\in son_u,v \ne w} f_{v,j-1}×f_{w,j-1}
很明显,第二中情况
而对于答案计算来说也存在两种情况: 首先对于三个点树上两两距离为d的情况都可以看成两个点在一个子树中,而另一个点“别处”
- 在子树
v 中选一个点,而在其他子树中选两个点:g_{u,j+1}×f_{v,j} - 在子树
v 中选两个点,而在其他子树中选一个点:f_{u,j-1}×g_{v,j}
同样为了避免重复计算只需要让其他子树搞成
计算状态数组和答案时,都有计算前面子树的情况,直接运用前缀和的思想即可边计算答案,边转移数组。
这里要提一点,我们至今没有提到
而其他博主在计算的时候加上
根据g的转移方程可知道:
然后就是长链剖分优化dp,每次保存长儿子的贡献,其他儿子暴力合并,每条长链都会在链头暴力合并一次时间复杂度
最后如果写指针版本的话,由于g数组是倒着转移的,
code
#define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr)
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
constexpr int N=5010;
int h[N],e[2*N],ne[2*N],idx;
void add(int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
int n;
int dep[N],son[N];
void dfs1(int u,int fa)
{
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i];
if(v==fa) continue;
dfs1(v,u);
if(dep[v]>dep[son[u]]) son[u]=v;
}
dep[u]=dep[son[u]]+1;
}
ll pool[4*N];
ll *f[N],*g[N],*now=pool;
ll ans;
void dfs2(int u,int fa)
{
f[u][0]=1;
if(son[u])
{
f[son[u]]=f[u]+1;
g[son[u]]=g[u]-1;
dfs2(son[u],u);
ans+=g[son[u]][1];// 加上重儿子的贡献
}
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i];
if(v==fa||v==son[u]) continue;
f[v]=now;now+=dep[v]<<1;
g[v]=now;now+=dep[v];
dfs2(v,u);
// 边计算
for(int j=0;j<dep[v];j++)
{
if(j) ans+=f[u][j-1]*g[v][j];
ans+=g[u][j+1]*f[v][j];
}
// 边转移
for(int j=0;j<dep[v];j++)
{
g[u][j+1]+=f[u][j+1]*f[v][j];
if(j) g[u][j-1]+=g[v][j];
f[u][j+1]+=f[v][j];
}
}
}
int main()
{
IO;
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs1(1,0);
f[1]=now;now+=dep[1]<<1;//多开一倍的内存
g[1]=now;now+=dep[1];
dfs2(1,0);
cout<<ans<<'\n';
return 0;
}
菜菜的我搞了一天这个题啊啊啊啊