P8386 [PA2021] Od deski do deski 题解

发布时间 2023-12-19 12:09:38作者: Creeper_l

显然是一道计数 dp。

dp 状态应该是最难的一部分了,个人认为这种状态设计得比较巧妙。如果像我刚开始一样设 \(dp_{i,j}\) 表示序列中一共有 \(i\) 个数,序列最后一个数为 \(j\) 的合法方案数的话,那么方程就会变得很不好转移,因为我们不知道当前的 \(j\) 和之前的某些数能不能匹配上,所以我们可以改变状态为 \(dp_{i,j}\) 表示序列中一共有 \(i\) 个数,在当前情况的序列后面加 \(j\) 种数能使序列变为合法序列的方案数。但是有一个问题,就是我们不知道当前合不合法,于是可以再加一维 \(0/1\) 表示当前合法或者不合法。

然后就是状态的转移了。首先需要知道两个很显然的性质:

  • 如果在序列后面加 \(j\) 种数可以使序列变的合法,那么加另外 \(m-j\) 种数就会变得不合法。

  • 并且如果序列 \(S\) 是合法的,那么形如 \(S+x+T+x\) 的序列一定是合法的,形如 \(S+x+T\) 的序列一定是不合法的,其中 \(T\) 是不包含 \(x\) 的任意序列。

于是就可以用刷表法进行转移了,具体转移方程如下:

\(\begin{cases}dp_{i+1,j,1}+=dp_{i,j,1}\times j \\dp_{i+1,j+1,0}+= dp_{i,j,1}\times (m-j) \\dp_{i+1,j,0}+=dp_{i,j,0}\times (m-j) \\dp_{i+1,j,1}+=dp_{i,j,0}\times j \end{cases}\)

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define inf 0x3f
#define inf_db 127
#define ls id << 1
#define rs id << 1 | 1
#define re register
#define endl '\n'
typedef pair <int,int> pii;
const int MAXN = 3e3 + 10;
const int mod = 1e9 + 7;
int n,m,dp[MAXN][MAXN][2],ans;
signed main()
{
	cin >> n >> m;
	dp[0][0][1] = 1;
	for(int i = 0;i < n;i++)
		for(int j = 0;j <= i;j++)
		{
			dp[i + 1][j][1] = (dp[i + 1][j][1] + dp[i][j][1] * j) % mod;
			dp[i + 1][j + 1][0] = (dp[i + 1][j + 1][0] + dp[i][j][1] * (m - j)) % mod;
			dp[i + 1][j][0] = (dp[i + 1][j][0] + dp[i][j][0] * (m - j)) % mod;
			dp[i + 1][j][1] = (dp[i + 1][j][1] + dp[i][j][0] * j) % mod;
		}
	for(int i = 0;i <= n;i++) ans = (ans + dp[n][i][1]) % mod;
	cout << ans;
	return 0; 
}