• 算法笔记--单调队列优化dp


    单调队列:队列中元素单调递增或递减,可以用双端队列实现(deque),队列的前面和后面都可以入队出队。

    单调队列优化dp:

    问题引入:

    dp[i] = min( a[j] ) ,i-m < j <= i

    普通的做法是O(nlogn),但是当n很大是,这个复杂度就不行了,考虑用单调队列优化来达到O(n)。

    单调队列优化dp时维护的一般都是两个值{ id(下标),value(值)},且它们都保持单调。

    对于这个问题,我们维护一个两个值都单调递增的序列。

    查询:队首不断删除,直到队首下标大于等于i - m + 1,队首就是答案。

    插入:因为要保证下标单调递增,所以从队尾加入元素a[i],因为又要保证值单调递增,所以我们不断删除队尾大于a[i]的元素,直到队尾小于a[i]或者队列为空,然后在队尾添加a[i]。

    为什么我们能直接删除队尾大于a[i]的元素呢?

    因为队尾删除的那些元素下标比a[i]小且值比a[i]大,如果这些元素可以是答案,那么a[i]肯定比他们好,所以这些值不会对答案产生贡献,所以直接删除就好了。

    最后,每个元素最多入队一次,出队一次,复杂度为O(n)。

    PS:只维护下标也可以,因为知道了下标就知道了值(如果你保存下来的话),不过如果n太大(以至于数组存不下)只能两个都维护了

    例1:POJ 2823

    代码:

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<deque>
    using namespace std;
    #define fi first
    #define se second
    #define pi acos(-1.0)
    #define LL long long
    #define mp make_pair
    #define pb push_back
    #define ls rt<<1, l, m
    #define rs rt<<1|1, m+1, r
    #define ULL unsigned LL
    #define pll pair<LL, LL>
    #define pii pair<int, int>
    #define mem(a, b) memset(a, b, sizeof(a))
    #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout);
    //head
    
    const int N = 1e6 + 5;
    int ans1[N], ans2[N];
    int main() {
        int n, k, x;
        scanf("%d %d", &n, &k);
        deque<pii>mn, mx;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &x);
            while(!mn.empty() && mn.back().se >= x) mn.pop_back();
            mn.pb(mp(i, x));
            while(!mn.empty() && mn.front().fi < i-k+1) mn.pop_front();
            ans1[i] = mn.front().se;
            while(!mx.empty() && mx.back().se <= x) mx.pop_back();
            mx.pb(mp(i, x));
            while(!mx.empty() && mx.front().fi < i-k+1) mx.pop_front();
            ans2[i] = mx.front().se;
        }
        for (int i = k; i <= n; i++) printf("%d%c", ans1[i], " 
    "[i==n]);
        for (int i = k; i <= n; i++) printf("%d%c", ans2[i], " 
    "[i==n]);
        return 0;
    }
    View Code

    例2:POJ - 3017

    思路:multiset + 单调队列优化

    单调队列维护的是下标递增的序列,但以这些下标为下标的数列是单调递减的,对于当前的i和队列中的一个下标tmp,他在队列中的上一下标为_tmp,则区间[_tmp+1,i]中的最大值为a[tmp],即转移方程为

    dp[i] = min(dp[_tmp] + a[tmp]),tmp属于单调队列,对于单调队列中的第一个元素tmp,_tmp为第一个使得sum[i] - sum[_tmp] >= m的位置

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<deque>
    #include<set>
    using namespace std;
    #define fi first
    #define se second
    #define pi acos(-1.0)
    #define LL long long
    #define mp make_pair
    #define pb push_back
    #define ls rt<<1, l, m
    #define rs rt<<1|1, m+1, r
    #define ULL unsigned LL
    #define pll pair<LL, LL>
    #define pii pair<int, int>
    #define mem(a, b) memset(a, b, sizeof(a))
    #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout);
    //head
    
    const int N = 1e5 + 5;
    LL sum[N], dp[N];
    int a[N];
    deque<LL>q;
    multiset<LL>s;
    int main() {
        int n;
        LL m;
        bool f = false;
        scanf("%d %lld", &n, &m);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            if(a[i] > m) f = true;
            sum[i] = sum[i-1] + a[i];
        }
        if(f) return 0*puts("-1");
        int p = 0;
        for (int i = 1; i <= n; i++) {
            while(sum[i] - sum[p] > m) p++;
            while(!q.empty() && a[q.back()] <= a[i]) {
                int tmp = q.back();
                q.pop_back();
                if(!q.empty()) s.erase(dp[q.back()] + a[tmp]);
            }
            if(!q.empty()) s.insert(dp[q.back()] + a[i]);
            q.push_back(i);
            while(!q.empty() && sum[i] - sum[q.front()-1] > m) {
                int tmp = q.front();
                q.pop_front();
                if(!q.empty()) s.erase(dp[tmp] + a[q.front()]);
            }
            dp[i] = dp[p]  + a[q.front()];
            if(s.size() != 0)dp[i] = min(dp[i], *s.begin());
            //cout << dp[i] << endl;
        }
        printf("%lld
    ", dp[n]);
        return 0;
    }
    View Code

    例3: POJ 2373

    思路:

    dp[0] = 0

    dp[i] = min {dp[j] + 1} 2*a <= i-j <= 2*b

    喷水半径R为整数,L为偶数,所以洒水机在整数点上

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<deque>
    #include<set>
    #include<cstring>
    using namespace std;
    #define fi first
    #define se second
    #define pi acos(-1.0)
    #define LL long long
    #define mp make_pair
    #define pb push_back
    #define ls rt<<1, l, m
    #define rs rt<<1|1, m+1, r
    #define ULL unsigned LL
    #define pll pair<LL, LL>
    #define pii pair<int, int>
    #define mem(a, b) memset(a, b, sizeof(a))
    #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout);
    //head
    
    const int N = 1e6 + 5;
    int dp[N], cnt[N], pre[N], suf[N];
    deque<int>q;
    int main() {
        int n, L, a, b, l, r;
        while( ~scanf("%d %d", &n, &L)) {
            mem(cnt, 0);
            scanf("%d %d", &a, &b);
            a *= 2;
            b *= 2;
            for (int i = 0; i < n; i++) {
                scanf("%d %d", &l, &r);
                cnt[l+1]++;
                cnt[r]--;
            }
            for (int i = 1; i <= L; i++) cnt[i] += cnt[i-1];
            q.clear();
            q.push_back(0);
            for (int i = 2; i <= L; i += 2) {
                if(cnt[i]) continue;
                while(!q.empty() && (i-q.front()) > b) {
                    q.pop_front();
                }
                if(q.empty() || i - q.front() < a) continue;
                dp[i] = dp[q.front()] + 1;
    
                while(!q.empty() && dp[q.back()] > dp[i]) q.pop_back();
                q.push_back(i);
            }
            if(dp[L])printf("%d
    ", dp[L]);
            else printf("-1
    ");
        }
        return 0;
    }
    View Code

     例4: 5429 多重背包

    思路: 单调队列优化多重背包

    代码:

    #pragma GCC optimize(2)
    #pragma GCC optimize(3)
    #pragma GCC optimize(4)
    #include<bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define pi acos(-1.0)
    #define LL long long
    //#define mp make_pair
    #define pb push_back
    #define ls rt<<1, l, m
    #define rs rt<<1|1, m+1, r
    #define ULL unsigned LL
    #define pll pair<LL, LL>
    #define pli pair<LL, int>
    #define pii pair<int, int>
    #define piii pair<pii, int>
    #define mem(a, b) memset(a, b, sizeof(a))
    #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout);
    //head
    
    const int N = 7e3 + 5;
    int dp[N];
    int v[N], w[N], c[N];
    int main() {
        int n, m;
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= n; i++) scanf("%d %d %d", &v[i], &w[i], &c[i]);
        for (int i = 1; i <= n; i++) {
            for (int p = 0; p < v[i]; p++) {
                int cnt = c[i];
                deque<pii> q;
                q.push_back({dp[p] + cnt*w[i], 0});
                for (int tot = 1; p + tot*v[i] <= m; tot ++) {
                    cnt--;
                    while(!q.empty() && tot - q.front().se > c[i]) q.pop_front();
                    while(!q.empty() && q.back().fi <= dp[p+tot*v[i]] + cnt*w[i]) q.pop_back();
                    q.push_back({dp[p+tot*v[i]] + cnt*w[i], tot});
                    dp[p+tot*v[i]] = q.front().fi + (tot - c[i]) * w[i];
                }
            }
        }
        printf("%d
    ", dp[m]);
        return 0;
    }
    View Code
  • 相关阅读:
    JAVA---JDK环境变量的配置
    “==” 与“equals(Object)”区别
    js替换字符串中所有斜杠
    uploadify学习笔记
    VBA学习笔记
    浮动导航条的实现
    canvas初识笔记
    EntityFramework存储过程的返回类型
    CSS及html的特殊字符表
    DIV六种实现元素水平居中
  • 原文地址:https://www.cnblogs.com/widsom/p/9298088.html
Copyright © 2020-2023  润新知