P3565 [POI2014]HOT-Hotels
参考题解
题目大意:
给定一棵树,在树上选 3 个点,要求两两距离相等,求方案数。
三个点树上两两距离为d存在下面两种情况
- 某个点三个子树(保证该点是LCA)中分别由三个点距离它为d
- 对于某一个点,它的 d 级祖先以及子树内两个以它为LCA,距它 d 的点
对于情况一,设计dp: fu,jf_{u,j}fu,j表示以uuu为根的子树,距i距离为jjj的点数
对于情况二,设计dp:gu,jg_{u,j}gu,j表示以uuu为根的子树,两个点的到LCA距离相等(此出用d表示),LCA到uuu的距离为d−jd-jd−j的点对
对于fu,jf_{u,j}fu,j的状态转移十分显然:fu,j=∑v∈sonufv,j−1f_{u,j}=\sum_{v\in son_u }f_{v,j-1}fu,j=∑v∈sonufv,j−1
而对于gu,jg_{u,j}gu,j来说存在两种情况
- 某个子树内部存在两个点:gu,j=∑v∈sonugv,j+1g_{u,j}=\sum_{v\in son_u}g_{v,j+1}gu,j=∑v∈sonugv,j+1
- 两个不同的子树各贡献一个点,以uuu为LCA:gu,j=∑v,w∈sonu,v≠wfv,j−1×fw,j−1g_{u,j}=\sum_{v,w\in son_u,v \ne w} f_{v,j-1}×f_{w,j-1}gu,j=∑v,w∈sonu,v=wfv,j−1×fw,j−1
很明显,第二中情况fv,j−1×fw,j−1,fw,j−1×fv,j−1f_{v,j-1}×f_{w,j-1}, f_{w,j-1}×f_{v,j-1}fv,j−1×fw,j−1,fw,j−1×fv,j−1是同一种情况,这里我们让vvv是uuu较靠前的儿子即可避免重复计算
而对于答案计算来说也存在两种情况:
首先对于三个点树上两两距离为d的情况都可以看成两个点在一个子树中,而另一个点“别处”
- 在子树vvv中选一个点,而在其他子树中选两个点:gu,j+1×fv,jg_{u,j+1}×f_{v,j}gu,j+1×fv,j
- 在子树vvv中选两个点,而在其他子树中选一个点:fu,j−1×gv,jf_{u,j-1}×g_{v,j}fu,j−1×gv,j
同样为了避免重复计算只需要让其他子树搞成vvv前面的子树即可
注意上述gu,j+1g_{u,j+1}gu,j+1和fu,j−1f_{u,j-1}fu,j−1都还未算vvv对其的贡献,这个只需要先计算答案在加贡献即可。
计算状态数组和答案时,都有计算前面子树的情况,直接运用前缀和的思想即可边计算答案,边转移数组。
这里要提一点,我们至今没有提到gu,0g_{u,0}gu,0对答案的贡献,它确实对答案有贡献,但是我们已经计算过了,如果在此加上会重复计算。
而其他博主在计算的时候加上gu,0g_{u,0}gu,0实际上增加的时gwson,1g_{wson,1}gwson,1即重儿子的贡献。
根据g的转移方程可知道:gu,0g_{u,0}gu,0的贡献全部来自于∑v∈sonugv,1\sum_{v\in son_u}g_{v,1}∑v∈sonugv,1,而计算fu,j−1×gv,jf_{u,j-1}×g_{v,j}fu,j−1×gv,j对答案的贡献时我们具体写一下fu,0×gv,1f_{u,0}×g_{v,1}fu,0×gv,1而fu,0=1f_{u,0}=1fu,0=1,因此已经计算过gu,0g_{u,0}gu,0的贡献!!!
然后就是长链剖分优化dp,每次保存长儿子的贡献,其他儿子暴力合并,每条长链都会在链头暴力合并一次时间复杂度O(len)O(len)O(len)总的合并时间复杂度O(n)O(n)O(n)
最后如果写指针版本的话,由于g数组是倒着转移的,fff要多开一倍的内存,否则g可能倒回来覆盖掉fff
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;
}
菜菜的我搞了一天这个题啊啊啊啊