Codeforces 1067A - Array Without Local Maximums 计数dp+详细推导 (Codeforces Round #518 (Div. 1))

26 篇文章 0 订阅
11 篇文章 0 订阅

CF: *2000  比起同难度级别的题,dp可真的难做,可能是我做dp的题太少了吧。。。。

 

题意:

给定一个长度为n(1~ 1e5)的序列a[],其中a[i] 应该在1-200中间,但是有的值看不到了,用-1表示,

但是知道的是对于 1 < i && i < n  这样的i :a[i] <= max(a[i-1], a[i+1]) ,并且 a[1] <= a[2],   a[n-1] >= a[n];

问这些不知道的数的所有的可能种数

 

思路:

显然是dp,状态就是每个数的值吧??

稍微做过点dp,直接想到的是 dp[i][j] 表示前i个数考虑完了,第i个数 数值为j时的所有种类数,但是考虑到状态转移(也就是i和i-1位置的种数关系),我们还要考虑一维表示当前数跟前一个数的大小关系,看似有三种关系:大于,等于,小于

其实:我们按照上述dp[i][j] 考虑的时候,输出的肯定是dp[n][] 这样的,因为规定了 a[n] <= a[n-1];所以我们可以把 <= 看作一个状态; 

所以有:dp[i][j][k] ,k=1时: 前i个数已经考虑完,第i个数值为 j,且第i个数>第i-1个数 时候的所有种类数;;;;;;;;;;dp[i][j][k] ,k=0时: 前i个数已经考虑完,第i个数值为 j,且第i个数<=第i-1个数 时候的所有种类数;;;;;;;;;

 

对于第i个数,我们分两种情况讨论:

如果有确定值的话,假设值为x,那只要考虑dp[i][x][]就好;  求dp[i][x][1] 时:就是把前i-1个中小于x的都加上,因为此时第i个数大于第i-1个数(方程式中第二维,我们要遍历的那个数),所以 dp[i-1][t][0] 和 dp[i-1][t][1]都要加上;

而在求dp[i][x][0] 时:对于t>x,加上dp[i-1][t][0] 因为 此时第i个数小于第i-1个数,;对于t=x,加上dp[i-1][t][0]+dp[i-1][t][1];

如果此时是-1的话,那我们的第2维 j 就要考虑等于1~200的所有情况,然而按照上述求某一个数的方式复杂度看似会上去,但我们运用前缀和,后缀和的想法,遍历的时候可以把前面所有的情况算进去;

 

 

 

 

#include<bits/stdc++.h>

using namespace std;

#define out fflush(stdout);
#define fast ios::sync_with_stdio(0),cin.tie(0);

#define FI first
#define SE second

typedef long long ll;
typedef pair<ll,ll> P;

const int maxn = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const ll mod = 998244353;


int n;
ll a[maxn];
ll dp[maxn][200+7][2] = {0};
// dp[i][j][k] 表示前i个数,第i个数为j时,跟前一个数大小关系为k时的最优解;k=1:>左边; k=0:<=左边


int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%lld", &a[i]);
    }

    if(a[1] == -1) {
        for(int j = 1; j <= 200; ++j) {
            dp[1][j][1] = 1;
        }
    }
    else {
        dp[1][a[1]][1] = 1;
    }
    
    for(int i = 2; i <= n; ++i) {
        if(a[i] == -1) {
            ll cur = 0;
            for(int j = 1; j <= 200; ++j) {
                dp[i][j][1] = cur;
                cur = (cur + (dp[i-1][j][1] + dp[i-1][j][0])) % mod;
            }
            cur = 0;
            for(int j = 200; j >= 1; --j) {
                cur = (cur + (dp[i-1][j][0])) % mod;
                dp[i][j][0] = (dp[i-1][j][1] + cur) % mod;
            }
        }
        else {
            for(int j = 1; j < a[i]; ++j) {
                dp[i][a[i]][1] = (dp[i][a[i]][1] + (dp[i-1][j][1] + dp[i-1][j][0])) % mod;
            }

            for(int j = 200; j >= a[i]; --j) {
                dp[i][a[i]][0] = (dp[i][a[i]][0] + dp[i-1][j][0]) % mod;;
            }
            dp[i][a[i]][0] = (dp[i][a[i]][0] + dp[i-1][a[i]][1]) % mod;
        }
    }
    ll ans = 0;
    if(a[n] == -1) {
        for(int i = 1; i <= 200; ++i) {
            ans = (ans + dp[n][i][0]) % mod;
        }
    }
    else {
        ans = dp[n][a[n]][0];
    }

    printf("%lld\n", ans);
    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值