• 单调队列/单调栈优化dp


    从这几篇博客学习的:
    DP优化小技巧(单调队列/单调栈)
    (单调队列优化DP) 代码源每日一题 Div1 选元素(数据加强版)
    算法学习笔记(67): 单调栈
    牛客多校第九场I (单调栈优化dp/单调栈的常用套路)

    一. 单调队列

    NC50528 滑动窗口

    在这里插入图片描述
    在这里插入图片描述

    主要思想:
    假设我们需要维护长度为 k k k的区间最大值,遍历过程中,对于一个数字 a i + 1 a_{i+1} ai+1,如果 a i + 1 > = a i a_{i+1} >= a_i ai+1>=ai,那么我们完全可以把 a i a_i ai的影响忽略掉。因为后面的数字比你并且生命周期还比你大,所以最大值永远不可能取到 a i a_i ai。具体在队列中的做法就是不断访问队尾元素,小于等于当前值就出队,结束后将当前元素入队。这样的话就可以保证队首元素一定是长度为 k k k的区间的最大值。(当然你需要判断队首元素与当前位置距离与 k k k的大小关系确定队首元素是否需要出队)。这个过程可以用deque很容易实现,数组模拟也很容易。
    AC代码:
    deque:

    #include 
    
    using namespace std;
    
    int n, k;
    int ar[1000050];
    deque<int> q;
    
    int main()
    {
        scanf("%d%d", &n, &k);
    
        for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
    
        for(int i = 1; i <= n; ++i)
        {
            while(!q.empty() && q.front() + k <= i) q.pop_front();
            while(!q.empty() && ar[q.back()] >= ar[i]) q.pop_back();
            q.push_back(i);
            if(i >= k) printf("%d ", ar[q.front()]);
        }
    
        putchar('\n');
    
        q.clear();
        for(int i = 1; i <= n; ++i)
        {
            while(!q.empty() && q.front() + k <= i) q.pop_front();
            while(!q.empty() && ar[q.back()] <= ar[i]) q.pop_back();
            q.push_back(i);
            if(i >= k) printf("%d ", ar[q.front()]);
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    数组模拟:

    #include 
    
    using namespace std;
    
    int n, k;
    int ar[1000050];
    int p[1000050];
    int l, r;//左闭右开
    
    int main()
    {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
        l = 0;
        r = 1;
        p[0] = 1;
        if(k == 1) printf("%d ", ar[1]);
        for(int i = 2; i <= n; ++i)
        {
            if(i - p[l] >= k && (l < r))    l++;
    
            while(r > l && ar[p[r - 1]] >= ar[i]) r--;
            p[r++] = i;
    
            if(i >= k)  printf("%d ", ar[p[l]]);
        }
        putchar('\n');
        l = 0;
        r = 1;
        p[0] = 1;
        if(k == 1)  printf("%d ", ar[1]);
        for(int i = 2; i <= n; ++i)
        {
            if(i - p[l] >= k && (l < r))    l++;
    
            while(r > l && ar[p[r - 1]] <= ar[i])    r--;
            p[r++] = i;
    
            if(i >= k)  printf("%d ", ar[p[l]]);
        }
        putchar('\n');
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    二. 单调队列优化dp

    当我们为了实现给动态规划的复杂度降维的时候,通常就需要单调栈/队列,通常用来维护前面状态下可以取到的最大值或者最小值,然后直接进行转移。(ygg)
    在这里插入图片描述

    1.daimayuan #875. 选元素(数据加强版)

    首先,我们可以看数据没加强版

    AcWing 4418. 选元素

    在这里插入图片描述
    在这里插入图片描述
    题意:
    就是对与每个长度为为 k k k的子区间,至少要有一个数被选择,一共可以选择 x x x个数字,目标是让选择的 x x x个数字最大。
    分析:
    考虑dp,定义dp[i][j]表示选了ar[i]的情况下,前 i i i个数字一共有 j j j个被选的情况下的最大值。
    状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − k , i − 1 ] [ j − 1 ] + a r [ i ] ) dp[i][j] =max(dp[i][j],dp[i-k,i-1][j-1]+ar[i]) dp[i][j]=max(dp[i][j],dp[ik,i1][j1]+ar[i])。复杂度 O ( n 3 ) O(n^3) O(n3)
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    #define int ll
    
    int n, k, x;
    int ar[205];
    int dp[205][205];
    int ans;
    
    signed main()
    {
        scanf("%lld%lld%lld", &n, &k, &x);
    
        for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]);
        memset(dp, 128, sizeof(dp));
        //cout << dp[0][0] << '\n';
        dp[0][0] = 0;
    
        for(int i = 1; i <= n; ++i)
        {
            for(int j = 1; j <= x; ++j)
            {
                for(int p = max(0ll, i - k); p < i; ++p)
                {
                    dp[i][j] = max(dp[i][j], dp[p][j - 1] + ar[i]);
                }
                //cout << i << ' ' << j << ' ' << dp[i][j] << '\n';
            }
        }
    
        ans = -1;
        for(int i = n - k + 1; i <= n; ++i) ans = max(ans, dp[i][x]);
        printf("%lld\n", ans);
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    数据加强版

    对于数据加强版, n , k , x n,k,x n,k,x的取值达到2500, O ( n 3 ) O(n^3) O(n3)一定超时。我们考虑优化,对于原来的状态转移式。 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − k , i − 1 ] [ j − 1 ] + a r [ i ] ) dp[i][j] =max(dp[i][j],dp[i-k,i-1][j-1]+ar[i]) dp[i][j]=max(dp[i][j],dp[ik,i1][j1]+ar[i]),考虑以极快的速度求出 m a x ( d p [ i − k , i − 1 ] [ j − 1 ] ) max(dp[i-k,i-1][j-1]) max(dp[ik,i1][j1])。本质上一个窗口大小为 k k k的滑动窗口,故利用单调队列优化即可。复杂度 O ( n 2 ) O(n^2) O(n2)
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    
    int n, k, x, top;
    ll ar[2550];
    ll dp[2550][2550];
    ll ans;
    
    int main()
    {
        scanf("%d%d%d", &n, &k, &x);
    
        for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]);
        memset(dp, 128, sizeof(dp));
        //cout << dp[0][0] << '\n';
        dp[0][0] = 0;
    
        for(int j = 1; j <= x; ++j)
        {
            deque<int> q;
            q.push_back(0);
            for(int i = 1; i <= n; ++i)
            {
                while(!q.empty() && q.front() < i - k) q.pop_front();
                dp[i][j] = dp[q.front()][j - 1] + ar[i];
                while(!q.empty() && dp[q.back()][j - 1] <= dp[i][j - 1]) q.pop_back();
                q.push_back(i);
            }
        }
    
        ans = -1;
        for(int i = n - k + 1; i <= n; ++i) ans = max(ans, dp[i][x]);
        printf("%lld\n", ans);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2. C. Jump and Treasure(Gym - 103743C)2022江苏省赛

    在这里插入图片描述
    在这里插入图片描述
    题意:
    输入 n , q , p n,q,p nqp,之后一行 n n n个数字,之后 q q q行, q q q次询问。
    你现在在玩一个游戏,初始在0点,你只可以向右走,游戏有很多关,对于第 x x x关,你只能走到 i i i的倍数的点上,并且每步跨越的最大距离是 p p p,每个点上有数字,当你到达这个点是,你的数值就会加上该点的权值,初始时你的数值为0,通关条件是到达点 n + 1 n+1 n+1及其之后的点,并且 n + 1 n+1 n+1及其之后的点的数值都为0,走到 n + 1 n+1 n+1之后的点不需要考虑他们是否是 x x x的倍数,只需要考虑最大跨越距离的限制。你可以在不违规的前提下走任意多步, q q q次询问,问你对于第 x x x关,通关时的最大数值。
    分析:
    考虑dp,定义dp[i]表示,对于第 x x x关,走到第 i i i个能到达的点时所能获得的最大价值。
    转移方程: d p [ i ] = m a x ( d p [ i ] , d p [ i − p / x , i − 1 ] + a r [ i ] ) dp[i] = max(dp[i], dp[i-p/x,i-1]+ar[i]) dp[i]=max(dp[i],dp[ip/x,i1]+ar[i]) (不考虑Noob的情况)。
    如果暴力跑,复杂度为 O ( T L E ) O(TLE) O(TLE),依旧考虑单调队列优化取 m a x max max的部分。复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    
    const  ll inf = 0x3f3f3f3f3f3f3f3f;
    int n, p, q, x;
    ll ans[1000050];
    int ar[1000050];
    ll dp[1000050];
    vector<int> vt[1000005];
    int qq[1000050];
    int l, r;
    
    int main()
    {
        io;
        cin >> n >> q >> p;
        for(int i = 1; i <= n; ++i) cin >> ar[i];
    
        for(int i = 1; i <= n; ++i)
        {
            ans[i] = -inf;
            for(int j = 0; j <= n; j += i) vt[i].push_back(j);
            vt[i].push_back(n + 1);
        }
    
        while(q--)
        {
            cin >> x;
            if(ans[x] != -inf) cout << ans[x] << '\n';
            else if(x > p) cout << "Noob\n";
            else
            {
                dp[0] = 0;
                l = r = 1;
                qq[r] = 0;
    
                for(int i = 1; i < vt[x].size(); ++i)
                {
                    while(l <= r && vt[x][i] - qq[l] > p) ++l;
                    dp[vt[x][i]] = dp[qq[l]] + ar[vt[x][i]];
    
                    while(l <= r && dp[qq[r]] <= dp[vt[x][i]]) --r;
                    qq[++r] = vt[x][i];
    
                }
    
                ans[x] = dp[n + 1];
                cout << ans[x] << '\n';
    
            }
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    3.P1725 琪露诺(单调队列)

    在这里插入图片描述
    题意:
    很类似上一个题,只不过跳的方式有所不同,是给定一个 l , r l,r l,r,对于当前点 i i i,可以跳到 [ i + l , i + r ] [i+l,i+r] [i+l,i+r]
    AC代码:

    #include 
    
    using namespace std;
    
    const int inf = 0x3f3f3f3f;
    int n, l, r;
    int ar[600050];
    int dp[600050];
    deque<int> q;
    
    int main()
    {
        scanf("%d%d%d", &n, &l, &r);
    
        for(int i = 0; i <= n; ++i)
        {
            scanf("%d", &ar[i]);
            dp[i] = -inf;
        }
    
        dp[0] = ar[0];
        q.push_back(0);
        dp[l] = ar[l];
    
    	//3*n是我乱写的,反正不会超时,后面那一坨的dp值都是相同的。
        for(int i = l + 1; i <= 3 * n; ++i)
        {
            while(!q.empty() && q.front() + r < i) q.pop_front();
            while(!q.empty() && dp[q.back()] <= dp[i - l]) q.pop_back();
            q.push_back(i - l);
    
            dp[i] = dp[q.front()] + ar[i];
            //cout << i << ' ' << dp[i] << '\n';
        }
    
        printf("%d\n", dp[3 * n]);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.AcWing 135 最大子序和

    在这里插入图片描述
    分析:
    dp[i]表示以i为结尾的一段长度不超过m的连续子序列的和的最大。
    d p [ i ] = max ⁡ j = i − m j = i − 1 s u m [ i ] − s u m [ j ] dp[i] = \max_{j=i-m}^{j=i-1} sum[i]-sum[j] dp[i]=maxj=imj=i1sum[i]sum[j]
    单调队列维护长度为m的区间中-sum[j]的最大值(或者sum[j]的最小值)即可 O ( 1 ) O(1) O(1)转移,总体复杂度 O ( n ) O(n) O(n)
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    #define int ll
    #define endl '\n'
    #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    
    const int maxn = 3e5 + 5;
    const int inf = 0x3f3f3f3f;
    int n, m;
    int ar[maxn];
    int sum[maxn];
    int dp[maxn];
    deque<pair<int,int>> q;
    bool flag;
    
    void print()
    {
        int ans = -inf;
        for(int i = 1; i <= n; ++i) ans = max(ans, ar[i]);
        cout << ans << '\n';
    }
    
    signed main()
    {
        //io;
        cin >> n >> m;
        flag = false;
        for(int i = 1; i <= n; ++i)
        {
            cin >> ar[i];
            sum[i] = sum[i - 1] + ar[i];
            if(ar[i] > 0) flag = true;
        }
    
        if(!flag) print();
        else
        {
            deque<int> q;
            q.push_back(0);
            for(int i = 1; i <= n; ++i)
            {
                while(!q.empty() && i - q.front() > m) q.pop_front();
                dp[i] = sum[i] - sum[q.front()];
                while(!q.empty() && -sum[i] >= -sum[q.back()]) q.pop_back();
                q.push_back(i);
                //cout << i << ' ' << dp[i] << '\n';
            }
    
            int mx = -inf;
            for(int i = 1; i <= n; ++i) mx = max(mx, dp[i]);
            cout << mx << '\n';
        }
    
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    5.AcWing 1087 修剪草坪

    在这里插入图片描述
    在这里插入图片描述
    分析:
    dp[i]表示前i头牛选第i头牛的最大效率
    d p [ i ] = max ⁡ j = i − k j = i − 1 d p [ j − 1 ] + s u m [ i ] − s u m [ j ] dp[i] = \max_{j = i-k}^{j = i-1} dp[j - 1] + sum[i] - sum[j] dp[i]=maxj=ikj=i1dp[j1]+sum[i]sum[j]
    单调队列维护dp[j-1]-sum[j]的最大值即可。
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    #define int ll
    #define endl '\n'
    #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    
    const int maxn = 3e5 + 5;
    const int inf = 0x3f3f3f3f;
    int n, k;
    int ar[maxn];
    int sum[maxn];
    int dp[maxn];
    int gg[maxn];
    
    signed main()
    {
        io;
        cin >> n >> k;
        for(int i = 1; i <= n; ++i)
        {
            cin >> ar[i];
            sum[i] = sum[i - 1] + ar[i];
        }
    
        deque<int> q;
        q.push_back(0);
        gg[0] = 0;
    
        for(int i = 1; i <= n; ++i)
        {
            while(!q.empty() && i - q.front() > k) q.pop_front();
            dp[i] = sum[i] + gg[q.front()];
            gg[i] = -sum[i] + dp[i - 1];
            while(!q.empty() && gg[i] >= gg[q.back()]) q.pop_back();
            q.push_back(i);
            //cout << i << ' ' << dp[i] << '\n';
        }
    
        int mx = -inf;
        for(int i = 1; i <= n; ++i) mx = max(mx, dp[i]);
        cout << mx << '\n';
    
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    6.AcWing 1090绿色通道

    在这里插入图片描述
    在这里插入图片描述
    分析:
    二分答案+单调队列优化dp
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    #define int ll
    #define endl '\n'
    #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    
    const int maxn = 2e5 + 5;
    const int inf = 0x3f3f3f3f;
    int n, m;
    int ar[maxn];
    int dp[maxn];
    
    bool judge(int k)
    {
        deque<int> q;
        q.push_back(0);
        dp[0] = 0;
        for(int i = 1; i <= n; ++i)
        {
            while(!q.empty() && i - q.front() > k) q.pop_front();
            dp[i] = dp[q.front()] + ar[i];
            while(!q.empty() && dp[i] <= dp[q.back()]) q.pop_back();
            q.push_back(i);
        }
    
        int mi = inf;
        for(int i = n; i > n - k; --i) mi = min(mi, dp[i]);
        return mi <= m;
    }
    
    signed main()
    {
        io;
        cin >> n >> m;
        for(int i = 1; i <= n; ++i) cin >> ar[i];
    
        int l = 0, r = n;
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(judge(mid)) r = mid - 1;
            else l = mid + 1;
        }
    
        cout << r << '\n';
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    #include 
    
    using namespace std;
    typedef long long ll;
    #define int ll
    #define endl '\n'
    #define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    
    const int maxn = 5e4 + 5;
    const int inf = 0x3f3f3f3f;
    int n, t;
    int ar[maxn];
    int sum[maxn];
    int gg[maxn];
    int dp[maxn];
    
    bool judge(int mid)
    {
        deque<int> q;
        memset(dp, 0, sizeof(dp));
        q.push_back(0);
        gg[0] = 0;
    
        for(int i = 1; i <= n; ++i)
        {
            while(!q.empty() && i - q.front() > mid) q.pop_front();
            dp[i] = sum[i] + gg[q.front()];
            gg[i] = -sum[i] + dp[i - 1];
            while(!q.empty() && gg[i] >= gg[q.back()]) q.pop_back();
            q.push_back(i);
        }
    
        int mx = -inf;
        for(int i = 1; i <= n; ++i) mx = max(mx, dp[i]);
        if(sum[n] - mx > t) return false;
        else return true;
    }
    
    signed main()
    {
        io;
        cin >> n >> t;
        for(int i = 1; i <= n; ++i)
        {
            cin >> ar[i];
            sum[i] = sum[i - 1] + ar[i];
        }
    
        if(sum[n] <= t) 
        {
            cout << 0 << '\n';
            return 0;
        }
    
        int l = 1, r = n, mid;
        while(l <= r)
        {
            mid = (l + r) >> 1;
            if(judge(mid)) r = mid - 1;
            else l = mid + 1;
        }
        cout << r + 1 << '\n';
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    三. 单调栈

    两大用处:
    1.NGE问题(Next Greater Element),也就是,对序列中每个元素,找到下一个比它大的元素。
    2.两元素间所有元素均(不)大/小于这两者

    1. P5788 【模板】单调栈

    在这里插入图片描述
    在这里插入图片描述
    (其实也可以用单调队列,不需要队首元素出队的单调队列)
    AC代码:

    #include 
    
    using namespace std;
    
    int n;
    int ar[3000050];
    stack<int> st;
    int ans[3000050];
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
    
        ans[n] = 0;
        st.push(n);
    
        for(int i = n - 1; i > 0; --i)
        {
            while(!st.empty() && ar[st.top()] <= ar[i]) st.pop();
    
            if(!st.empty()) ans[i] = st.top();
            else ans[i] = 0;
    
            st.push(i);
        }
    
        for(int i = 1; i <= n; ++i) printf("%d ", ans[i]);
        putchar('\n');
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.P1823 [COI2007] Patrik 音乐会的等待

    在这里插入图片描述
    在这里插入图片描述
    (区间1~i内元素大小关系和单调栈内元素情况)
    在这里插入图片描述
    在这里插入图片描述
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    #define int ll
    
    int n;
    int ar[500050];
    stack<pair<int, int> > st;
    pair<int, int> pi;
    int ans, cnt;
    
    signed main()
    {
        scanf("%lld", &n);
        for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]);
    
        st.push({ar[1], 1});
        for(int i = 2; i <= n; ++i)
        {
            int cnt = 1;
            while(!st.empty() && st.top().first <= ar[i])
            {
                pi = st.top();
                st.pop();
                //cout << pi.first << ' ' << pi.second << '\n';
                ans += pi.second;
                if(pi.first == ar[i]) cnt = pi.second + 1;
                else cnt = 1;
            }
            if(!st.empty()) ++ans;
            st.push({ar[i], cnt});
            //cout << i << ' ' << ans << '\n';
        }
    
        printf("%lld\n", ans);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    四. 单调栈优化dp

    1.CF1313C2 Skyscrapers (hard version)

    在这里插入图片描述
    在这里插入图片描述
    题意:
    输入一个 n n n,之后输入 n n n个数字 a r [ i ] ar[i] ar[i]
    让你选择一个点作为山峰。假设选择点 p o s pos pos作为山峰,其他的 n − 1 n-1 n1点的高度将发生变化。假设修改后的高度记为 h [ i ] h[i] h[i],对于1~pos-1中的一个点i,必须满足 h [ i ] = m i n ( h [ i + 1 ] , a r [ i ] ) h[i]=min(h[i+1],ar[i]) h[i]=min(h[i+1],ar[i]),对于pos+1~n中的一个点i,必须满足 h [ i ] = m i n ( h [ i − 1 ] , a r [ i ] ) h[i] = min(h[i-1],ar[i]) h[i]=min(h[i1],ar[i])。求 ∑ 1 n h [ i ] \sum_1^n h[i] 1nh[i]的最大值。输出取最大值时的h[i]数组。
    分析:
    首先,我们可以看一下easy版本。区别在于n的大小。easy版本n最大1000。 O ( n 2 ) O(n^2) O(n2)能过。我们考虑dp,定义dp1[i],dp2[i]分别表示选择 i i i作为山峰时 i i i极其左侧的最大值。答案就是 m a x ( d p 1 [ i ] + d p 2 [ i ] − a r [ i ] ) max(dp1[i]+dp2[i]-ar[i]) max(dp1[i]+dp2[i]ar[i])
    我们考虑快速求dp1[i]的方法,假设ar[j] i i i之前第一个满足ar[j]<=ar[i]的元素,那么 d p 1 [ i ] = d p 1 [ j ] + ( i − j ) ∗ a r [ i ] dp1[i]=dp1[j]+(i-j)*ar[i] dp1[i]=dp1[j]+(ij)ar[i]。而对于第一个小于等于的元素,显然可以利用单调栈快速找出。(dp2求法同理)复杂度 O ( n ) O(n) O(n)
    (单调栈用法1,利用单调栈找到区间内第一个比他大/小的元素)
    AC代码:

    #include 
    
    using namespace std;
    typedef long long ll;
    
    int n, pos;
    ll ar[500050];
    ll dp1[500050];
    ll dp2[500050];
    ll ans[500050];
    ll mx;
    stack<ll> st;
    
    void work()
    {
        int tmp = pos;
        ll mxx = ar[tmp];
        ans[tmp] = ar[tmp];
        while(tmp > 0)
        {
            --tmp;
            ans[tmp] = min(mxx, ar[tmp]);
            mxx = ans[tmp];
        }
    
        tmp = pos;
        mxx = ar[tmp];
        while(tmp < n)
        {
            ++tmp;
            ans[tmp] = min(mxx, ar[tmp]);
            mxx = ans[tmp];
        }
    
        for(int i = 1; i <= n; ++i) printf("%lld ", ans[i]);
        putchar('\n');
    }
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]);
    
        st.push(0);
        for(int i = 1; i <= n; ++i)
        {
            while(!st.empty() && ar[st.top()] >= ar[i]) st.pop();
    
            dp1[i] = dp1[st.top()] + (i - st.top()) * ar[i];
            st.push(i);
        }
    
        while(!st.empty()) st.pop();
        st.push(n + 1);
        for(int i = n; i > 0; --i)
        {
            while(!st.empty() && ar[st.top()] >= ar[i]) st.pop();
    
            dp2[i] = dp2[st.top()] + (st.top() - i) * ar[i];
            st.push(i);
        }
    
        for(int i = 1; i <= n; ++i)
        {
            if(dp1[i] + dp2[i] - ar[i] > mx)
            {
                mx = dp1[i] + dp2[i] - ar[i];
                pos = i;
            }
        }
    
        work();
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    在这里插入图片描述
    (295是正解,换成解绑之后的cin,cout用线段树暴力维护居然卡过了)。

    2.CF1407D Discrete Centrifugal Jumps

    在这里插入图片描述
    在这里插入图片描述
    题意:
    输入一个n,之后n个数字ar[i]
    一开始你在位置1,你要到达位置n,从位置i到位置j必须满足一下条件之一。
    在这里插入图片描述
    问你到达n最少需要跳几步。
    分析:
    单调栈的用法2,找两元素间所有元素均(不)大/小于这两者
    在这里插入图片描述
    (区间1~i内元素大小关系和单调栈内元素情况)
    借助这个图理解和P1823 [COI2007] Patrik 音乐会的等待这个题理解一下。
    AC代码:

    #include 
    
    using namespace std;
    
    int n;
    stack<int> st1, st2;
    int dp[300050];
    int ar[300050];
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
    
        memset(dp, 0x3f, sizeof(dp));
    
        dp[1] = 0;
        for(int i = 1; i <= n; ++i)
        {
            dp[i] = min(dp[i], dp[i - 1] + 1);
            while(!st1.empty() && ar[st1.top()] <= ar[i])
            {
                int pos = st1.top();
                st1.pop();
                if(!st1.empty() && ar[i] > ar[pos]) dp[i] = min(dp[st1.top()] + 1, dp[i]);
            }
            st1.push(i);
    
            while(!st2.empty() && ar[st2.top()] >= ar[i])
            {
                int pos = st2.top();
                st2.pop();
                if(!st2.empty() && ar[i] < ar[pos]) dp[i] = min(dp[st2.top()] + 1, dp[i]);
            }
            st2.push(i);
        }
    
        printf("%d\n", dp[n]);
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    3.牛客多校第九场I The Great Wall II

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    题意:
    输入一个n,之后输入数组ar[n]。你需要将区间[1,n]分成k个子区间,这k个区间两两不交,且并集是全集。每个子区间的值是这个区间中最大的那个数字,你需要最小化这k个区间的值的和。输出k1~n时对应的答案。
    分析:
    官方题解:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    说人话:
    在这里插入图片描述

    AC代码:

    #include 
    
    using namespace std;
    
    const int inf = 0x3f3f3f3f;
    int n;
    int ar[8005];
    int dp[8005][8005];
    struct node
    {
        int val, mi, mi_dp;//ar[k],dp[k][j - 1],min(dp[k][j])
    }st[8005];
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &ar[i]);
    
        memset(dp, 0x3f, sizeof(dp));
    
        dp[0][0] = 0;
    
        for(int j = 1; j <= n; ++j)
        {
            int top = 1;
            st[top] = {inf, inf, inf};
            for(int i = 1; i <= n; ++i)
            {
                int mi = dp[i - 1][j - 1];
                while(top >= 1 && st[top].val <= ar[i]) mi = min(mi, st[top--].mi);
                st[top + 1] = {ar[i], mi, min(mi + ar[i], st[top].mi_dp)};
                ++top;
                dp[i][j] = st[top].mi_dp;
            }
            printf("%d\n", dp[n][j]);
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
  • 相关阅读:
    【操作系统-IO管理】IO层次结构
    共享股东系统实现资源共享,让有资源的人成为店铺分红股东
    【图像融合】差异的高斯:一种简单有效的通用图像融合方法[用于融合红外和可见光图像、多焦点图像、多模态医学图像和多曝光图像](Matlab代码实现)
    C++类与对象(下)
    在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素。
    Linux发布Java项目,使用screen窗口
    Docker Compose介绍和安装
    【Hack The Box】windows练习-- Timelapse
    公司电脑强制休眠的3种解决方案
    【Hack The Box】windows练习-- ServMon
  • 原文地址:https://blog.csdn.net/qq_33969563/article/details/127668440