BZOJ1925: [Sdoi2010]地精部落 滚动数组DP

以数字个数 i i i作为阶段,表示 1 1 1~ i i i的排列

f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示 1 1 1 i i i的排列以 j j j结尾,第三维为 0 0 0表示峰 为 1 1 1表示谷

则容易写出一个方程 f [ i ] [ j ] [ 0 ] = ∑ k = 1 j − 1 f [ i − 1 ] [ k ] [ 1 ] f[i][j][0] = \sum_{k = 1}^{j-1}f[i-1][k][1] f[i][j][0]=k=1j1f[i1][k][1]
以及 f [ i ] [ j ] [ 1 ] = ∑ k = j + 1 i f [ i − 1 ] [ k ] [ 0 ] f[i][j][1] = \sum_{k = j + 1}^{i}f[i-1][k][0] f[i][j][1]=k=j+1if[i1][k][0]

注意到第二个方程从上一个阶段转移过来时,第 i − 1 i-1 i1个阶段中没有 f [ i − 1 ] [ i ] [ 0 ] f[i-1][i][0] f[i1][i][0]这个状态

但是在第 i i i个阶段时, i i i是可以放在前 i − 1 i-1 i1个元素中的 上面的方程无法统计这个状况

处理方法如下:

注意到i比其他元素都要大,所以i可以代替任何一个其他的峰 方案数同该数作为峰时的方案数

所以我们可以在答案中加上 f [ i − 1 ] [ j ] [ 0 ] f[i-1][j][0] f[i1][j][0]表示把 i i i放在位置 i − 1 i-1 i1作为峰的方案数 这样计数是不重不漏的

所以 第二个方程变为 f [ i ] [ j ] [ 1 ] = ∑ k = j i f [ i − 1 ] [ k ] [ 0 ] f[i][j][1] = \sum_{k = j}^{i} f[i-1][k][0] f[i][j][1]=k=jif[i1][k][0]

这样转移时间复杂度 O ( n 3 ) O(n^3) O(n3) 空间复杂度 O ( n 2 ) O(n^2) O(n2) 都不太能接受

解决方法与 N O I P 2015 NOIP2015 NOIP2015子串一题类似
观察到 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1] f [ i ] [ j + 1 ] [ 0 / 1 ] f[i][j+1][0/1] f[i][j+1][0/1]的方程有重叠,我们可以直接从同阶段与 j j j相邻的状态转移,并用滚动数组优化
最终 f [ i ] [ j ] [ 0 ] = f [ i ] [ j − 1 ] [ 0 ] + f [ i − 1 ] [ j − 1 ] [ 1 ] f[i][j][0]=f[i][j-1][0]+f[i-1][j-1][1] f[i][j][0]=f[i][j1][0]+f[i1][j1][1]
f [ i ] [ j ] [ 1 ] = f [ i ] [ j + 1 ] [ 1 ] + f [ i ] [ j ] [ 0 ] f[i][j][1]=f[i][j+1][1]+f[i][j][0] f[i][j][1]=f[i][j+1][1]+f[i][j][0]
使用滚动数组后
时间复杂度 O ( n 2 ) O(n^2) O(n2) 空间复杂度 O ( n ) O(n) O(n)
代码如下

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
#define maxn 4207
#define maxm 1000001
#define V e[i].v
#define ull unsigned long long
int n, m, k, tot;
inline int read(){
    int s = 0, f = 1; char c = getchar();
    while (c > '9' || c < '0') {if(c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') {s = s * 10 + c - '0'; c = getchar();}
    return s * f;
}
int f[2][maxn][2];
int ans, p;
int main(){
    n = read(), p = read();
    //p = 1e9+7;
    f[1][1][0] = f[1][1][1] = 1;
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            f[i&1][j][0] = 0;
            /*for (int k = 1; k < j; k++) {
                f[i&1][j][0] += f[(i - 1)&1][k][1];
                f[i&1][j][0] %= p;
            }*/
            f[i&1][j][0] = f[i&1][j-1][0] + f[(i-1)&1][j-1][1];
            f[i&1][j][0] %= p;
        }
        for (int j = i; j >= 1; j--) {
            f[i&1][j][1] = 0;
            f[i&1][j][1] = f[i&1][j+1][1] + f[(i-1)&1][j][0];
            f[i&1][j][1] %= p;
        }
    }
    for (int i = 1; i <= n; i++) {
        ans += f[n&1][i][0] + f[n&1][i][1];
        ans %= p;
    }
    printf("%d\n", ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值