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 {jchoose 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 {jchoose 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-1choose 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-1choose 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-1choose 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
",f[n][m]);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14992391.html