题目链接:Bzoj1925 .
—————————————-
概述
题目大意如下。
给定一个数
n
,问
其中,
—————————————-
分析
作为一个 DP 蒟蒻,这题折磨了我很久。
首先明确一点,这是一个计数问题,应该可以 DP (钦定算法2333)。那么接着的问题就是状态的选取了,这题的状态选取范围太广泛,不同的状态选取复杂度都不一样,我接下来讲一讲我在网上学习的做法,代码很短很巧妙。
定义
dp[i][j]
为
1
~
对于
对于
对于
状态的含义讲完了,接下来是转移。假设当前序列不合法的数有
j
个,那么插入一个数得到的新序列的不合法数只可能是
那么我们考虑不合法数的改变情况,可以得到如下转移:
dp[i+1][j]=dp[i+1][j]+dp[i][j] ;
我们将 i+1 插入到当前序列的末尾或者最后一个数前面,这两个位置中一定有且仅有一个不会改变新序列不合法数的个数,而且能使新序列不合法数的个数不变的位置只可能是这两个之一。
dp[i+1][j−1]=dp[i+1][j−1]+dp[i][j]∗j ;
我们将 i+1 插入到当前序列不合法数的前面,可以使得新序列不合法数-1.
dp[i+1][j+1]=dp[i+1][j+1]+dp[i][j]∗(i−j) ;
我们将 i+1 插入到当前序列剩下的位置,可以使新序列不合法数+1.
转移就是这样,最后答案乘2即可。
—————————————-
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define For(i, j, k) for(int i = j; i <= (int)k; ++ i)
#define Forr(i, j, k) for(int i = j; i >= (int)k; -- i)
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 4200 + 5;
int n;
ll mo;
ll dp[maxn][maxn];
int main(){
scanf("%d%lld", &n, &mo);
dp[1][0] = 1;
For(i, 1, n)
For(j, 0, i){
(dp[i+1][j] += dp[i][j]) %= mo;
(dp[i+1][j-1] += dp[i][j]*j) %= mo;
(dp[i+1][j+1] += dp[i][j]*(i-j)%mo) %= mo;
}
printf("%lld", (dp[n][0]<<1) % mo);
return 0;
}
—————————————-
小结
这题的方法其实还有很多,但是这一种比较简洁所以选择学一学,可能不太好理解吧。设好状态之后,解题的关键就是分位置讨论新的序列的不合法数,是道思维好题。
—————————————-
——wrote by miraclejzd.