Codeforces 487b Strip, dp + RMQ(经典)

10 篇文章 0 订阅
8 篇文章 0 订阅
  • 题意:有一个长度为n的数列,问是否能把这个数列切成连续的几段,使得 1.每一段长度大于等于l;2.每一段中的最大值和最小值之差小于等于s。如果能输出能切成的最小的段数,不能输出-1。
  • 思路:非常经典的dp题目,假设dp[i]表示 a1...ai 这个序列能切成的最小段数,如果不能dp[i] = INF。现在问题是

    1.如何找到状态转移方程。考虑如果l = 3, s = 1,如果 ai=5 , ai1=3 , 很明显 a1...ai 无论如何都不能切成满足条件的小段,所以dp[i]=INF。再考虑如果对于 ai , ajk...ai , aj...ai ,都是满足条件的一段,很容易想到dp[i] = min(dp[j-k]…dp[j]) + 1。 2. 有了状态转移方程,现在问题便是如何维护当前区间的最大值最小值以及与 ai 配对,满足条件的dp[j-k]…dp[j]的最小值,即RMQ问题。这里用ST表,线段树都可以,但是因为这里可以采用尺取法(二指针),区间是连续移动的(没有跳跃),所以还可以使用multiset 和 单调队列 来维护。因为这里n 只有 105 所以使用multiset的复杂度是nlogn完全没有问题,使用单调队列的话是O(n),但是如果单调队列用标准库的deque实现的话,常数通常会比较大,对于数据量比较大的题目还是推荐自己手写。

  • 代码:

/*************************************************************************
 *Multiset 版本
 ************************************************************************/

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 5;
const int INF = 0x3f3f3f3f;
int A[maxn];
int dp[maxn];

int main()
{
    int n, s, l;
    scanf("%d %d %d", &n, &s, &l);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &A[i]);
    }
    multiset<int> pre, cur;
    int i = 1, j = 1;
    for(i = 1; i <= n; i++) {
        cur.insert(A[i]);
        while(*cur.rbegin() - *cur.begin() > s) {
            cur.erase(cur.find(A[j]));
            if(pre.find(dp[j - 1]) != pre.end()) {
                pre.erase(pre.find(dp[j - 1]));
            }
            j++;
        }
        if(i - j + 1 >= l) pre.insert(dp[i - l]);
        if(pre.empty()) {
            dp[i] = INF;
        } else {
            dp[i] = *pre.begin() + 1;
        }
    }
    int ans = dp[n];
    if(ans >= INF) ans = -1;
    cout << ans << endl;
    return 0;
}

/*************************************************************************
 * 单调队列版本
 ************************************************************************/

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 100000 + 5;
int A[maxn];
int dp[maxn];

deque<int> min_que, max_que, pre_que;
void add(int x) {
    while(!min_que.empty() && x < min_que.back()) min_que.pop_back();
    min_que.push_back(x);
    while(!max_que.empty() && x > max_que.back()) max_que.pop_back();
    max_que.push_back(x);
}

void del(int x) {
    if(!min_que.empty() && x == min_que.front()) min_que.pop_front();
    if(!max_que.empty() && x == max_que.front()) max_que.pop_front();
}

void pre_add(int x) {
    while(!pre_que.empty() && x < pre_que.back()) pre_que.pop_back();
    pre_que.push_back(x);
}
void pre_del(int x) {
    if(!pre_que.empty() && pre_que.front() == x) pre_que.pop_front();
}
int main()
{
    int n, s, l;
    scanf("%d %d %d", &n, &s, &l);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &A[i]);
    }

    int i = 1, j = 1;
    for(i = 1; i <= n; i++) {
        add(A[i]);
        while(max_que.front() - min_que.front() > s) {
            del(A[j]);
            pre_del(dp[j-1]);
            j++;
        }
        if(i - j + 1 >= l) pre_add(dp[i-l]);
        if(pre_que.empty()) dp[i] = INF;
        else dp[i] = pre_que.front() + 1;
    }
    int ans = dp[n];
    if(ans >= INF) ans = -1;
    cout << ans << endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值