原题: https://www.luogu.org/problemnew/show/P2467
题意:
1~n这n个数的排列,要求“此起彼伏”,问方案数%P
解析:
发现自己已经独立做出两题省选难度题了,之前遇到省选一脸懵逼来着。可能是省选题越来越水了吧~
因为所有数都不一样,所以数之间只有大小关系,即1,2,3和1,100,105其实是一样的。
这样有什么好处:
- 假设轮到了一个数,对于剩下的数,只需要记录有几个大的即可(小的可以通过总数-大的算出,省一维)
那么写出数组涵义,
,sta==0
表示当前点为下凹点,反之上凸;i表示比当前数大的数量。
转移方程:
- 下凹转上凸选一个大的:
- 上凸转下凹选一个小的:
以大数为例,其维护过程如下:
计算时间复杂度:循环维护O(n),每次选择大数数量O(n),选择目标大数数量O(n),n==4000,O(n^3)就炸了。
观察上图,你会发现后面个O(n)可以轻松合到一起。只有5更新4,5还会更新4以下的所有,所以可以累加一个更新,一路往下做。
int sum=0;
for(int i=n-now;i>=1;i--){
sum=(sum+dp[f][sta][i])%mod;
dp[!f][!sta][i-1]=(dp[!f][!sta][i-1]+sum)%mod;
}
再考虑小数
和大数不同,小的是往上累加(43210,4321,432,43,4)
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n, mod;
int dp[2][2][4300];
//f 滚动数组 sta=0 当前为下凹 i 比当前数大的数量
int main() {
scanf("%d%d", &n, &mod);
int f = 1;
for(int sta = 0; sta <= 1; sta++) {
for(int i = 0; i < n; i++) {
dp[f][sta][i] = 1;
}
}
for(int now = 1; now < n; now++) {
int sta = 0;
int sum = 0;
for(int i = n - now; i >= 1; i--) {
sum = (sum + dp[f][sta][i]) % mod;
dp[!f][!sta][i - 1] = (dp[!f][!sta][i - 1] + sum) % mod;
}
sta = 1;
sum = 0;
for(int i = n - now; i >= 1; i--) { // 小的数量
// 下一个状态也是 to(大的数量) ,因为选的是小的,最小值不会变
int to = n - now - i;
sum = (sum + dp[f][sta][to]) % mod;
dp[!f][!sta][to] = (dp[!f][!sta][to] + sum) % mod;
}
f = !f;
memset(dp[!f], 0, sizeof(dp[!f]));
}
printf("%d\n", (dp[f][0][0] + dp[f][1][0]) % mod);
}