P2467 [SDOI2010]地精部落(dp n个数此起彼伏)

原题: https://www.luogu.org/problemnew/show/P2467

题意:

1~n这n个数的排列,要求“此起彼伏”,问方案数%P

在这里插入图片描述
解析:

发现自己已经独立做出两题省选难度题了,之前遇到省选一脸懵逼来着。可能是省选题越来越水了吧~

因为所有数都不一样,所以数之间只有大小关系,即1,2,3和1,100,105其实是一样的。

这样有什么好处:

  • 假设轮到了一个数,对于剩下的数,只需要记录有几个大的即可(小的可以通过总数-大的算出,省一维)

那么写出数组涵义, d p [ ] [ s t a ] [ i ] dp[滚动数组][sta][i] sta==0表示当前点为下凹点,反之上凸;i表示比当前数大的数量。

转移方程:

  • 下凹转上凸选一个大的: d p [ ! f ] [ ! s t a ] [ j < i ] < < d p [ f ] [ s t a ] [ i ] dp[!f][!sta][j<i]<<dp[f][sta][i]
  • 上凸转下凹选一个小的: d p [ ! f ] [ ! s t a ] [ j < = n n o w i ] < < d p [ f ] [ s t a ] [ i ] dp[!f][!sta][j<=n-now-i]<<dp[f][sta][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);
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/87872649