单调栈,单调队列

单调栈解决的是以某个值为最小(最大)值的最大区间,实现方法是:求最小值(最大值)的最大区间,维护一个递增(递减)的栈,当遇到一个比栈顶小的值的时候开始弹栈,弹栈停止的位置到这个值的区间即为此值左边的最大区间;同时,当一个值被弹掉的时候也就意味着比它更小(更大)的值来了,也可以计算被弹掉的值得右边的最大区间。

下面给出单调栈的类型及它所对应的求的东西

单调不升:该区间左边的数严格小于它,右边的数小于等于它

单调不降:该区间左边的数严格大于它,右边的数大于等于它

单调上升:该区间左边的数大于等于它,右边的数严格大于它

单调下降:该区间左边的数小于等于它,右边的数严格小于它

该代码求以给出的每个值为最大值的最大区间的左右端点。(该区间左边的数严格小于它,右边的数小于等于它)

https://cn.vjudge.net/contest/289215#problem/A

单调栈模板题,对于每个点,找左边比他小的人中最大的那个人的下标,右边同样。

所以维护一个单调递减的栈就行。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;

const int maxm = 5e4 + 5;
const int inf = 1e9 + 7;
int a[maxm];
int t, n;
stack<int> st;
int l[maxm], r[maxm];
int main() {
scanf("%d", &t);
int cnt = 0;
while(t--) {
    while(!st.empty()) st.pop();
    memset(l, 0, sizeof(l));
    memset(r, 0, sizeof(r));
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++) {
        while(!st.empty() && a[st.top()] <= a[i]) {
            l[i] = st.top();
            st.pop();
        }
        st.push(i);
    }
    while(!st.empty()) st.pop();
    for(int i = n; i >= 1; i--) {
        while(!st.empty() && a[st.top()] <= a[i]) {
            r[i] = st.top();
            st.pop();
        }
        st.push(i);
    }
    printf("Case %d:\n", ++cnt);
    for(int i = 1; i <= n; i++) {
        printf("%d %d\n", l[i], r[i]);
    }
}

return 0;
}

 

 

  单调队列解决的是区间最小(最大)值,实现方法是:求区间最小(最大)值,就维护一个递增的双端队列,队中保存原始序列的标号,当即将入队的元素的值比队尾的元素的值小(大)的时候就不断弹掉队尾,知道出现比它更小的值,当即将入队的元素队首元素的跨度(即将入队元素的序号到队首元素序列的区间)大于规定区间时就不断弹掉队首,直到跨度小于或等于所规定的区间。如此可保证队首元素为最小(最大)值,(但不能保证队尾就是原始序列中的最大(最小)值),并维护区间长度。

单调不降(或上升):区间最小值

单调不升(或下降):区间最大值

https://cn.vjudge.net/contest/289215#problem/G

单调队列模板题

求固定长度区间内的最大值与最小值。



#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int maxm = 1e6 + 5;

int n, k;
int a[maxm];
int deq[maxm];
int mi[maxm], ma[maxm];
int main() {
    while(~scanf("%d%d", &n, &k)) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        int s = 0, t = 0;
        for(int i = 1; i <= n; i++) {
            while(s < t && a[deq[t - 1] ] >= a[i]) t--;
            deq[t++] = i;
            if(i - k + 1 >= 1) {
                mi[i - k + 1] = a[deq[s] ];
                if(deq[s] == i - k + 1) s++;
            }
        }
        s = 0, t = 0;
        for(int i = 1; i <= n; i++) {
            while(s < t && a[deq[t - 1] ] <= a[i]) t--;
            deq[t++] = i;
            if(i - k + 1 >= 1) {
                ma[i - k + 1] = a[deq[s] ];
                if(deq[s] == i - k + 1) s++;
            }
        }
        for(int i = 1; i <= n - k + 1; i++) {
            printf("%d%c", mi[i], i == n - k + 1 ? '\n' : ' ');
        }
        for(int i = 1; i <= n - k + 1; i++) {
            printf("%d%c", ma[i], i == n - k + 1 ? '\n' : ' ');
        }
    }

    return 0;
}

 

 

https://cn.vjudge.net/contest/289215#problem/E

单调队列题

题意:一个序列,对于每个长度为m的序列,求出从左到右最大值变化次数以及最后的最大值。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int maxm = 1e7 + 5;
typedef long long ll;
int n, m, k;
ll p, q, r, mod;
int T;
ll a[maxm];
ll deq[maxm];
const int inf = 1e9 + 7;
int main() {
scanf("%d", &T);
while(T--) {
    scanf("%d%d%d%lld%lld%lld%lld", &n, &m, &k, &p, &q, &r, &mod);
    for(int i = 1; i <= k; i++) {
        scanf("%lld", &a[i]);
    }
    for(int i = k + 1; i <= n; i++) {
        a[i] = (p * a[i - 1] + q * i + r) % mod;
    }
    int s = 1, t = 1;
    ll res1 = 0, res2 = 0;
    for(int i = n; i >= 1; i--) {
        while(s < t && a[deq[t - 1] ] <= a[i]) t--;
        deq[t++] = i;
        if(i + m - 1 <= n) {
            res1 += a[deq[s] ] ^ i;
            res2 += (t - s) ^ i;
            if(deq[s] == i + m - 1) {
                s++;
            }
        }
    }
    printf("%lld %lld\n", res1, res2);
}
return 0;
}

 

 

Neko's loop

 HDU - 6444 

https://vjudge.net/contest/289215#problem/J 专题链接

题意:给你n个数排列成环(你可以任选一个点作为起点),你最多跳m次,每次跳k个数。跳到一个数就加上这个数字(可以多次跳这个数),求最后最大能得到的值是多少。如果大于s输出0,否则输出s-你的最大值。

思路:找寻环节+长度不超过L的最大子段和。首先用若干个vector存每个循环节的元素。然后对于每个循环节,我们分两种情况讨论:假设循环节长度为sz,循环节总和为sum

1、m不能整除sz。则先计算长度不超过m%sz的最大子段和。然后加上(m/sz)*sum。(在纸上画一下,好好想想)

2、m能整除sz。此时如果按第一种情况考虑的话就相当于固定从第一个点开始跳一直跳完正好(m/sz)个循环节。

所以我们不一定要从第一个点开始跳。于是先求一下长度不超过sz的最大子段和(相当于找一个起点),再加上((m/sz)-1)*sum。(其实我们能使它最大化的地方就是起跳的地方到sz和当前循环节第一个位置到停的地方那两段距离)

然后两种取个最大值就行了。可以直接同时计算两种情况,相当于不能整除的时候最后剩余的几步就不用跳了,并且去最后一圈的最大值(考虑有负数的情况),整除的时候第一种情况余数为0。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<iostream>
#include<set>
#include<vector>
using namespace std;
const int maxm = 1e4 + 5;
typedef long long ll;
ll a[maxm], q[2 * maxm];
ll st[maxm];
ll sum_a[maxm], sum_b[maxm];
vector<ll> ve[maxm];
int vis[maxm];
int t;
int n;
ll s, m, k;
//这里维护的是一个单调递增的单调队列。
//单调队列里的元素是下标,每个下标表示的是前缀和,和里面是元素不同的是,如果是元素,那么队列里面两个下标之间的元素比后面这个下标对应的元素要大,对于是前缀和的话,也表示更大,但是前缀和开始大,
//后来小,那么表示中间存在负数元素。 所以是当前点的前缀和减去之前满足距离条件的最小前缀和,所以可以用单调队列来维护。 ll cal(vector
<ll>& v, ll tmp) { ll res = 0; ll sz = v.size(); ll s = 0, t = 0; for(ll i = 0; i < sz; i++) q[i] = q[i + sz] = a[v[i] ];
//这里是单调队列,相当于去<= tmp长度的连续和最大。
for(ll i = 0; i < 2 * sz; i++) { if(i == 0) sum_b[i] = q[i]; else sum_b[i] = sum_b[i - 1] + q[i]; if(i < tmp) res = max(res, sum_b[i]); while(s < t && st[s] + tmp < i) s++; if(s < t) res = max(res, sum_b[i] - sum_b[st[s] ]); while(s < t && sum_b[st[t - 1] ] >= sum_b[i]) t--; st[t++] = i; } return res; } ll solve(vector<ll>& v, int in) { int sz = v.size(); if(sz == 0) return 0ll; ll ant = sum_a[in]; ll x = m / (ll)sz; ll y = m % (ll)sz; ll m1 = cal(v, (ll)sz); m1 += max(0ll, ant) * ((x - 1) >= 1 ? (x - 1) : 0ll); ll m2 = cal(v, (ll)y); m2 += max(0ll, ant) * x; return max(m1, m2); } int main() { scanf("%d", &t); int cnt = 0; while(t--) { memset(sum_a, 0, sizeof(sum_a)); memset(sum_b, 0, sizeof(sum_b)); memset(vis, 0, sizeof(vis)); int ind = 0; scanf("%d%lld%lld%lld", &n, &s, &m, &k); for(int i = 0; i < n; i++) ve[i].clear(); for(int i = 0; i < n; i++) { scanf("%lld", &a[i]); } ll maxx = 0; for(int i = 0; i < n; i++) { int j = i; // ve[ind].push_back(j); // vis[j] = 1; while(!vis[j]) { vis[j] = 1; ve[ind].push_back(j); sum_a[ind] += a[j]; j = (j + k) % n; } maxx = max(solve(ve[ind], ind), maxx); ind++; } // printf("Case #%d: %lld\n", ++cnt, maxx); if(maxx > s) maxx = 0; else maxx = s - maxx; printf("Case #%d: %lld\n", ++cnt, maxx); } return 0; }

 https://vjudge.net/contest/293001#status/xiayuyang/D/0/

求一个点左右比他大的点为父节点,然后有两个比他大的话就取最近的那个,如果一样近,就去最大的那个,取两边最大的,要想到单调栈!!!!!

然后注意先按坐标点排序,所以id要注意一下。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxm =2e5 + 5;
int n;

struct point {
int pos, val, ind;
bool operator < (const point &p) {
    return pos < p.pos;
}
} p[maxm];
stack<int> s1, s2;
struct node {
int l, lv, lind;
int r, rv, rind;
} nn[maxm];
pii c[maxm];
int main() {
scanf("%d", &n);
for(int i  = 1; i <= n; i++) {
    scanf("%d%d", &p[i].pos, &p[i].val);
    p[i].ind = i;
}
sort(p + 1, p + 1 + n);
for(int i = 1; i <= n; i++) {
    while(!s1.empty() && p[s1.top()].val <= p[i].val) {
        s1.pop();
    }
    if(!s1.empty()) {
        nn[i].l = p[s1.top()].pos;
        nn[i].lv = p[s1.top()].val;
        nn[i].lind = p[s1.top()].ind;
    }
    s1.push(i);
}
for(int i = n; i >= 1; i--) {
    while(!s2.empty() && p[s2.top()].val <= p[i].val) {
        s2.pop();
    }
    if(!s2.empty()) {
        nn[i].r = p[s2.top()].pos;
        nn[i].rv = p[s2.top()].val;
        nn[i].rind = p[s2.top()].ind;
    }
    s2.push(i);
}
for(int i = 1; i <= n; i++) {
    if(nn[i].lv == 0 && nn[i].rv == 0) {
        c[i].second = -1;
        c[i].first = p[i].ind;
    }
    else if(nn[i].lv == 0) {
        c[i].second = nn[i].rind;
        c[i].first = p[i].ind;
    }
    else if(nn[i].rv == 0) {
        c[i].second = nn[i].lind;
        c[i].first = p[i].ind;
    }
    else {
        if(p[i].pos - nn[i].l < nn[i].r - p[i].pos) {
            c[i].second = nn[i].lind;
            c[i].first = p[i].ind;
        }
        else if(p[i].pos - nn[i].l == nn[i].r - p[i].pos) {
            if(nn[i].lv >= nn[i].rv) c[i].second = nn[i].lind, c[i].first = p[i].ind;
            else c[i].second = nn[i].rind, c[i].first = p[i].ind;
        }
        else c[i].second = nn[i].rind, c[i].first = p[i].ind;;
    }
}
sort(c + 1, c + n + 1);
for(int i = 1; i <= n; i++) {
    printf("%d%c", c[i].second, i == n ? '\n' : ' ');
}
return 0;
}

 

 

单调队列优化dp

 

 琪露诺:https://www.luogu.org/problemnew/show/P1725

   解题思路:用dp[i]表示到达点i时获得的最大冰冻指数,dp[i]=max{dp[i-j]}+a[i],l<=j<=r<=i.

        然后对于求max{dp[i-j]}用单调队列来优化。

#include<bits/stdc++.h>

using namespace std;
const int N = 2e5 + 5;
typedef pair<int, int> pii;
int n, l, r;
int a[N], deq[N], dp[N];
int main() {
    scanf("%d%d%d", &n, &l, &r);
    for(int i = 0; i <= n; i++) scanf("%d", &a[i]);
    dp[0] = a[0];
    int s = 0, t = 0;
    for(int i = l; i <= n; i++) {
        while(s < t && i - deq[s] > r) s++;
        while(s < t && dp[deq[t - 1] ] <= dp[i - l]) t--;
        deq[t++] = i - l;
        dp[i] = dp[deq[s] ] + a[i];
    }
    int ans = 0;
    for(int i = n - r + 1; i <= n; i++) ans = max(ans, dp[i]);
    printf("%d\n", ans);
    return 0;
}

 

 

 

最大连续和:https://loj.ac/problem/10176

    给你一个长度为 n 的整数序列,要求从中找出一段连续的长度不超过 m 的子序列,使得这个序列的和最大。

 

例题解答:

  首先考虑DP方程:用dp[i]表示以i为结尾的长度不超过m的最大子序列和。

   转移为:dp[i]=max{sum[i]-sum[i-k],k=1.2....m}

            =sum[i]-min{sum[i-k],k=1.2....m};

   最后转变为对于所有的1<=k<=m,找出所有sum[i-k]的最小值。

   考虑用单调队列来维护决策值sum[i-k]就行啦。

注意这里初始化s = 0, t  = 1是为了保存sum[0]的结果。

#include<bits/stdc++.h>

using namespace std;
const int N = 2e5 + 5;
typedef pair<int, int> pii;
int n, m;
int sum[N], deq[N];
int main() {
    scanf("%d%d", &n, &m);
    int x, ans = -1e9;
    for(int i = 1; i <= n; i++) scanf("%d", &x), sum[i] = sum[i - 1] + x;
    int s = 0, t = 1;
    for(int i = 1; i <= n; i++) {
        while(s < t && i - deq[s] > m) s++;
        ans = max(ans, sum[i] - sum[deq[s] ]);
        while(s < t && sum[deq[t - 1] ] >= sum[i]) t--;
        deq[t++] = i;
    }
    printf("%d\n", ans);
    return 0;
}

 

https://vijos.org/p/1243

https://blog.csdn.net/hjf1201/article/details/78729320

题目描述
在经过一段时间的经营后,dd_engi的OI商店不满足于从别的供货商那里购买产品放上货架,而要开始自己生产产品了!产品的生产需要M个步骤,每一个步骤都可以在N台机器中的任何一台完成,但生产的步骤必须严格按顺序执行。由于这N台机器的性能不同,它们完成每一个步骤的所需时间也不同。机器i完成第j个步骤的时间为T[i,j]。把半成品从一台机器上搬到另一台机器上也需要一定的时间K。同时,为了保证安全和产品的质量,每台机器最多只能连续完成产品的L个步骤。也就是说,如果有一台机器连续完成了产品的L个步骤,下一个步骤就必须换一台机器来完成。现在,dd_engi的OI商店有史以来的第一个产品就要开始生产了,那么最短需要多长时间呢?
某日Azuki.7对跃动说:这样的题目太简单,我们把题目的范围改一改
对于菜鸟跃动来说,这是个很困难的问题,他希望你能帮他解决这个问题

输入输出格式
第一行有四个整数M, N, K, L 下面的N行,每行有M个整数。
第I+1行的第J个整数为T[J,I]。
输出只有一行,表示需要的最短时间。

设f[i][j]为生产前i个产品且第i个产品在j机器上生产的最小代价。
那么很容易想到的动态转移方程是f[i][j]=min(f[k][p]+sum[k+1][i][j])其中p!=j且k<<i。
sum[i][j][k]表示从第i件到第j件都在k机器上完成的单价和,然而显然可以用前缀和优化,式子再变成
f[i][j]=min(f[k][p]+sum[i][j]-sum[k][j])。这显然是难以维护的。
但是我们发现题目中j的个数很少,那么对于每一个确定的j(假如j==1)的时候,sum[i][j]显然是一个定值,f[i][1]=min(f[k][p]-sum[k][1])+sum[i][1],那么我们需要做的是维护从i-l+1到i区间内f[k][p]-sum[k][1]的最小值。
那么显然可以用单调队列优化。

 

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;
typedef long long ll;
const ll INF = 2e18;
ll m, n, cost, L;

ll dp[10][N];
ll deq[10][N][2];
ll sum[10][N];
int s[10], t[10];
int main() {
    scanf("%lld%lld%lld%lld", &m, &n, &cost, &L);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            scanf("%lld", &sum[i][j]);
            sum[i][j] += sum[i][j - 1];
        }
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) dp[i][j] = INF;
    }
    for(int i = 1; i <= n; i++) deq[i][t[i] ][0] = 0, t[i] = 1;
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= n; j++) {
            while(s[j] < t[j] && i - deq[j][s[j] ][0] > L) s[j]++;
            int num = deq[j][s[j] ][0], pos = deq[j][s[j] ][1];
            dp[j][i] = min(dp[j][i], dp[pos][num] + sum[j][i] - sum[j][num] + cost);
        }
        for(int k = 1; k <= n; k++) {
            for(int j = 1; j <= n; j++) {
                if(k != j) {
                    while(s[j] < t[j] && dp[k][i] - sum[j][i] <= dp[deq[j][t[j] - 1 ][1] ][deq[j][t[j] - 1 ][0] ] - sum[j][deq[j][t[j] - 1][0]])
                        t[j]--;
                    deq[j][t[j] ][0] = i, deq[j][t[j]++ ][1] = k;
                }
            }
        }
    }
    ll ans = INF;
    for(int i = 1; i <= n; i++) ans = min(ans, dp[i][m]);
    cout << ans - cost << endl;
    return 0;
}

 

 

http://acm.hdu.edu.cn/showproblem.php?pid=4374

题意:简化一下,有一个n*m的矩阵,每格有一个分数,一个人站在(1,x)位置,在每一行中,他只能朝一个方向走(向左或向右),且最多走t步,问走到最后第n行得到的最大分数。

思路:dp[i][j]表示i行 j列位置的最大分数,若从左到右,dp[i][j] = max(dp[i-1][k]+sum[j]-sum[k-1]);若从右到左,dp[i][j] = max(dp[i-1][k] +sum[j] - sum[k+1])。sum[j]在两个方程中分别表示该行 1~j的和与 j~m的和。

上述两个方程可以分别写成:

dp[i][j] = max(dp[i-1][k] - sum[k-1]) + sum[j];

dp[i][j] = max(dp[i-1][k] - sum[k+1]) + sum[j]; ( abs(j-k) <= t)

可以看出dp[i][j] 是与 sum[j]无关,而只与 dp[i-1][k] - sum[k-1] 或 dp[i-1][k] - sum[k+1] 有关。因为在(i,j)处,它最多由(i,j-t)处跳过来,每求一个dp[i][j]只需添加一个新的dp[i-1][k] - sum[k-1] 或 dp[i-1][k] - sum[k+1] 即可,而把到(i,j)的前一个点放到单调队列中,最后在单调队列中取队首就是最大值。

 

 

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int inf=1<<29;
const int maxn=110;
const int maxm=10010;
struct Node
{
    int val;
    int pos;
    Node(){}
    Node(int sval,int spos):val(sval),pos(spos){}
}q[maxm];
int n,m,x,t,sum[maxn][maxm],dp[maxn][maxm];
int main()
{
    while(scanf("%d%d%d%d",&n,&m,&x,&t)!=EOF)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                int val;
                scanf("%d",&val);
                sum[i][j]=sum[i][j-1]+val;
            }
        for(int i=1;i<=m;i++)
            dp[1][i]=-inf;
        dp[1][x]=sum[1][x]-sum[1][x-1];
        for(int i=1;i<=t;i++)
        {
            if(i>=x)
                break;
            dp[1][x-i]=sum[1][x]-sum[1][x-i-1];
        }
        for(int i=1;i<=t;i++)
        {
            if(x+i>m)
                break;
            dp[1][x+i]=sum[1][x+i]-sum[1][x-1];
        }
        for(int i=2;i<=n;i++)
        {
            int pre=0,last=0;
            for(int j=1;j<=m;j++)
            {
                while(pre<last&&q[pre].pos<j-t)
                    pre++;
                while(pre<last&&(dp[i-1][j]-sum[i][j-1])>q[last-1].val)
                    last--;
                q[last++]=Node(dp[i-1][j]-sum[i][j-1],j);
                dp[i][j]=sum[i][j]+q[pre].val;
            }
            pre=0,last=0;
            for(int j=m;j>=1;j--)
            {
                while(pre<last&&q[pre].pos-j>t)
                    pre++;
                while(pre<last&&dp[i-1][j]+sum[i][j]>q[last-1].val)
                    last--;
                q[last++]=Node(dp[i-1][j]+sum[i][j],j);
                dp[i][j]=max(dp[i][j],q[pre].val-sum[i][j-1]);
            }
        }
        int ans=-inf;
        for(int i=1;i<=m;i++)
            ans=max(ans,dp[n][i]);
        printf("%d\n",ans);
    }
    return 0;
}

 

posted @ 2019-03-25 21:29  downrainsun  阅读(221)  评论(0编辑  收藏  举报