分析:
树上排列计数不止一次遇到了,我们依然考虑DP
设(f_{i,j})表示以(i)为根的子树下面的点,在排列中形成了(j)的连续段,这(j)个连续段的相对位置确定的贡献总和
合并依然是树上背包的方式合并,枚举已合并的部分(i)段,将要合并的子树(j)段,得到(k)段,所合并的根(u)便是合并位置的LCA
这里的复杂度时(O(n^3))
现在的问题是(i)段和(j)段合并成(k)段的方案数
设(g_{i,j,k})为(i)段和(j)段合并成(k)段的方案数
我们尝试从(k-1)推到(k)
枚举新的一段由(i)中的(t)段和(j)中的(t)段合并(可能是(i)在前面,也可能是(j)在前面,一共两种情况)
或者(i)中的(t)段和(j)中的(t+1)段合并
或者(i)中的(t+1)段和(j)中的(t)段合并
列出式子:
[g_{i,j,k}=sum_{t} 2g_{i-t,j-t,k-1}+g_{i-t-1,j-t,k-1}+g_{i-t,j-t-1,k-1}
]
发现枚举(t)时(i-j)始终不变,我们可以把值存在(sum_{i-j})中优化一下
这里的复杂度也是(O(n^3))
总复杂度(O(n^3)),然而我的跑很慢。。。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 505
#define MOD 1000000007
using namespace std;
inline int getint()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
int n;
int fir[maxn],nxt[maxn],to[maxn],cnt;
int f[maxn][maxn],g[maxn][maxn][maxn],pw[maxn][maxn],sum[maxn<<1],tmp[maxn];
int sz[maxn];
inline void newnode(int u,int v)
{to[++cnt]=v,nxt[cnt]=fir[u],fir[u]=cnt;}
inline int upd(int x){return x<MOD?x:x-MOD;}
inline void dfs(int u,int dpt)
{
sz[u]=1;f[u][1]=1;
for(int i=fir[u];i;i=nxt[i])
{
int v=to[i];dfs(v,dpt+1);
for(int j=1;j<=sz[u]+sz[v];j++)tmp[j]=0;
for(int j=1;j<=sz[u];j++)for(int k=1;k<=sz[v];k++)
for(int t=max(1,j-k);t<=j+k;t++)tmp[t]=(tmp[t]+1ll*g[j][k][t]*f[u][j]%MOD*f[v][k]%MOD*pw[dpt][j+k-t])%MOD;
sz[u]+=sz[v];
for(int j=1;j<=sz[u];j++)f[u][j]=tmp[j];
}
}
int main()
{
n=getint();
for(int i=2;i<=n;i++)newnode(getint(),i);
for(int i=1;i<=n;i++)
{
pw[i][0]=1;
for(int j=1;j<=n;j++)pw[i][j]=1ll*pw[i][j-1]*i%MOD;
}
g[0][0][0]=1;
for(int k=1;k<=n;k++)
{
memset(sum,0,sizeof sum);
for(int i=0;i<=n;i++)for(int j=0;i+j<=n;j++)
{
if(i+j>=k)g[i][j][k]=(2ll*sum[i-j+n]+sum[i-j+n-1]+sum[i-j+n+1])%MOD;
sum[i-j+n]=upd(sum[i-j+n]+g[i][j][k-1]);
}
}
dfs(1,1);
printf("%d
",f[1][1]);
}