Path Counting题解
题目大意:给定一颗以 \(1\) 为根,深度为 \(n\) 的有根树(规定根的深度为 \(1\)),树上每一个深度为 \(i\) 的点都有 \(a_i\) 个儿子。对于每一个 \(k \in [1,2n-2]\) 求出长度为 \(k\) 的路径数量。
数据范围:\(2 \le n \le 5000\),\(2 \le a_i \le 10^9\)。
路径分为两种,一种为两端点的LCA在路径中间(不在端点上),另一种为两端点的LCA在端点上,我们分别求解,然后将路径数量相加。
LCA在路径中间
我们考虑在LCA处统计路径数。
设 \(f_{i,j}\) 表示以深度为 \(i\) 的点为LCA的路径中,长度为 \(j\) 的有多少条。
我们考虑如何计算 \(f_{i,j}\),有一个非常显然的式子,就是枚举左右端点的深度,然后计算满足这两个端点的LCA深度为 \(i\) 的方案数,即:
式子的含义就是先选出该节点的两个儿子,然后从这两个儿子向下扩展路径,一个扩展 \(k-1\) 长度,另外一个扩展 \(j-k-1\),可以简单前缀积优化一下做到 \(O(N^3)\)。
需要进行优化,注意到 \(f_{i,j}\) 式子右边的式子和 \(f_{i+1,j-2}\) 的很像,我们把这两个式子放在一起。
发现 \(f_{i,j}\) 的大部分贡献都是从 \(\frac{a_i \times (a_i-1)} 2 \times f_{i+1,j-2} \times \frac{2a_{i+1}}{a_{i+1}-1}\),除去 \(k=1\) 和 \(k=i+j-1\) 的情况,这两种情况用前缀积算上就好了。
最后,\(f_{i,j}\) 乘上深度为 \(i\) 的点的数量就是这一部分对于答案的贡献了。
LCA在端点
这个更好做了,LCA在端点的路径中长度为 \(i\) 的只有可能在深度大于 \(i\) 的点取到,统计每个深度有多少个点即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
const int mod=1000000007;
int n;
int a[maxn];
int f[maxn][maxn*2],g[maxn*2],inv[maxn];
int power(int x,int y)
{
int ans=1;
while(y)
{
if(y&1)
ans=1ll*ans*x%mod;
y>>=1;
x=1ll*x*x%mod;
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
scanf("%d",&a[i]),inv[i]=power(a[i]-1,mod-2);
f[n-1][2]=1ll*a[n-1]*(a[n-1]-1)/2%mod;
for(int i=n-2;i>=1;i--)
{
for(int p=2*n-2;p>=2;p--)
f[i][p]=1ll*a[i]*(a[i]-1)/2%mod*f[i+1][p-2]%mod*inv[i+1]%mod*2ll%mod*a[i+1]%mod;//从f[i+1][p-2]转移贡献
//加上k=1和k=i+j-1的贡献
f[i][2]=(f[i][2]+1ll*a[i]*(a[i]-1)/2%mod)%mod;
int cnt=1;
for(int p=3;p<=2*n-2;p++)
{
cnt=1ll*cnt*a[i+p-2]%mod;
f[i][p]=(f[i][p]+1ll*a[i]*(a[i]-1)%mod*cnt%mod)%mod;
}
}
int cnt=1,sum=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=2*n-2;j++)
g[j]=(g[j]+1ll*cnt*f[i][j]%mod)%mod;//计算对答案的贡献
sum=(sum+cnt)%mod;
cnt=1ll*cnt*a[i]%mod;
}
cnt=1;int num=0;
for(int i=1;i<n;i++)
num=(num+cnt)%mod,g[i]=(0ll+g[i]+sum-num+mod)%mod,cnt=1ll*cnt*a[i]%mod;//LCA在端点的路径
for(int i=1;i<=2*n-2;i++)
printf("%d ",g[i]);
puts("");
return 0;
}