CF1439D INOI Final Contests
一、题目
二、解法
给的 \(O(n^3)\) 的数据范围,可以考虑计数 \(dp\)
一种显然的思路是依次考虑人的位置,比如我们考虑最后一个人的最终位置,但是初始位置到最终位置之间是坐满了人的,剩下左右两边又要重新分配人,划分子问题太复杂了,有点难做。
但是我们可以反过来考虑格子,设 \(f[i][j]\) 表示 \(i\) 个格子 \(j\) 的人的总贡献(人有编号顺序),\(g[i][j]\) 表示总方案数,当 \(i>j\) 时一定会有一个格子空出来,而且考虑边缘的格子是限制最小的,所以可以枚举最后一个空格子,这个空格子是不可越过的,所以就有了两个子问题,用组合数把人划分到两边去即可:
\[g[i][j]=g[i-1][j]+\sum_{k>0} g[i-k-1][j-k]\cdot g[k][k]\cdot {j\choose k}
\]
\[f[i][j]=f[i-1][j]+\sum_{k>0}(g[i-k-1][j-k]\cdot f[k][k]+f[i-k-1][j-k]\cdot g[k][k])\cdot {j\choose k}
\]
但是 \(i=j\) 的情况要另当别论,我们考虑最后一个人的最终位置是 \(j\),因为没有空格我们只需要保证其他人不会越过 \(j\) 即可,显然我们得到了两个子问题,设 \(s(n)=\sum_{i=1}^n i\),转移:
\[g[i][i]=(i+1)\cdot \sum_{j=1}^ig[j-1][j-1]\cdot g[i-j][i-j]\cdot{i-1\choose j-1}
\]
\[f[i][i]\leftarrow (s(j-1)+s(i-j))\cdot g[j-1][j-1]\cdot g[i-j][i-j]\cdot {i-1\choose j-1}
\]
\[f[i][i]\leftarrow (f[j-1][j-1]\cdot g[i-j][i-j]+f[i-j][i-j]\cdot g[j-1][j-1])\cdot (i+1)\cdot{i-1\choose j-1}
\]
最后一个人起点和选方向的方案数是 \(i+1\)
三、总结
这道题技巧性还是很强的,\(dp\) 的时候选取考虑对象时不要局限;转移的时候注意子问题的划分,比如本题的转移就是利用题目的限制设置分界线,使得两个子问题通过该分界线分开;最后是转移的时候选限制最小的方式转移。
最后解释一下为什么本题不适用等概率环模型,我现在就只见到了两种适用情况:
- 题目可以修改描述方式使得符合该模型,可以轻易的算出合法的方案数。
- 如果容易算全局序列的方案数,但是单点的方案数算不出来,可以主动转成环,通过讨论得到环的方案数,由于每个单点等概率的性质,可以把总方案数等概率分配到每个单点上。
这种题是很灵活的,所以还是不要乱转,这道题转了好像真的没什么用。
#include <cstdio>
#define int long long
const int M = 505;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,p,f[M][M],g[M][M],C[M][M];
void init()
{
C[0][0]=1;
for(int i=1;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
}
}
int sum(int x)
{
return x*(x+1)/2%p;
}
signed main()
{
n=read();m=read();p=read();
init();g[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
int t=g[j-1][j-1]*g[i-j][i-j]%p*C[i-1][j-1]%p;
int s=(f[j-1][j-1]*g[i-j][i-j]+f[i-j][i-j]*g[j-1][j-1])%p;
g[i][i]=(g[i][i]+(i+1)*t)%p;
f[i][i]+=(sum(j-1)+sum(i-j))*t%p;
f[i][i]+=(i+1)*C[i-1][j-1]%p*s%p;
f[i][i]%=p;
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++)//start from 0
{
f[i][j]=f[i-1][j],g[i][j]=g[i-1][j];
for(int k=1;k<=j;k++)
{
g[i][j]=(g[i][j]+g[k][k]*g[i-k-1][j-k]%p*C[j][k])%p;
f[i][j]+=(f[i-k-1][j-k]*g[k][k]+f[k][k]*g[i-k-1][j-k])%p*C[j][k]%p;
f[i][j]%=p;
}
}
printf("%lld\n",f[n][m]);
}