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]);
}
posted @ 2021-07-09 20:35  C202044zxy  阅读(365)  评论(0编辑  收藏  举报