P8386 [PA2021] Od deski do desk 题解
考虑一个大的序列一定被分成几个区间来删除。朴素的 dp 定义是 \(dp_{i,j}\) 表示前 \(i\) 个数,最后一个数元素是 \(j\) 的方案数。然而这样不仅不好转移,而且设不下状态。不难发现所有值是等价的。考虑这样一个事情:若我们要分出一个新的区间,那么这个区间的前一部分的区间需要同样合法,原序列是一个排列,位置和元素是一一对应的。于是我们只需要知道分割后前面有几个区间合法以及保证区间结尾后的第一个数和新填的数相同。
于是我们设 \(dp_{i,j,0/1}\) 表示前 \(i\) 位,有 \(j\) 个位置使得其位置 \(w_k\) 满足 \([1,w_k]\) 是合法的,当前序列合法与否。
那么 dp 的转移是显然的:
\[\begin{gather*} dp_{i+1,j,1}\leftarrow dp_{i,j,1}\times j\\ dp_{i+1,j+1,0}\leftarrow dp_{i,j,1}\times(m-j)\\ dp_{i+1,j,1}\leftarrow dp_{i,j,0}\times j\\ dp_{i+1,j,0}\leftarrow dp_{i,j,0}\times (m-j) \end{gather*} \]含义就是选择填和这些位置相同的数或是不同的数,选择 \(j\) 意味着选择了合法的状态,反之亦然。
代码:
#include <bits/stdc++.h>
#define N 3005
#define int long long
#define mod 1000000007
using namespace std;
int n, m;
int dp[N][N][2];
void add(int &x, int y) {
x += y;
if (x > mod)
x -= mod;
}
signed main() {
cin >> n >> m;
dp[0][0][1] = 1;
for (int i = 0; i < n; i++)
for (int j = 0; j <= n; j++) {
add(dp[i + 1][j][1], dp[i][j][1] * j % mod);
add(dp[i + 1][j + 1][0], dp[i][j][1] * (m - j) % mod);
add(dp[i + 1][j][1], dp[i][j][0] * j % mod);
add(dp[i + 1][j][0], dp[i][j][0] * (m - j) % mod);
}
int ans = 0;
for (int i = 0; i <= n; i++)
add(ans, dp[n][i][1]);
cout << ans << "\n";
return 0;
}
标签:do,leftarrow,int,题解,Od,times,区间,dp
From: https://www.cnblogs.com/Rock-N-Roll/p/18468504