单调栈,单调队列,倍增专题

D - Largest Submatrix of All 1’s( POJ3494 )

题意:

​ 给定n*m的01矩阵,求全是1的最大子矩阵面积

思路:

​ 单调栈,

​ 对每一行都做一次单调栈求当前高度为最小的区间,(当前高度是从第一行到第i行连续的有多少个1,这个1要包括在当前这一行)

​ 具体看代码实现.

​ 然后就是普通的求面积了,只不过求了n次

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 2e3 + 10;
int h[N][N], st[N], top;
int a[N][N];
int n, m;
int L[N], R[N];
void get_LR(int i) {
    top = 0;
    h[i][0] = 0;
    st[0] = 0;
    for (int j = 1; j <= m; j++) {
        while (top > 0 && h[i][j] <= h[i][st[top]]) top--;
        L[j] = st[top] + 1;
        st[++top] = j;
    }

    top = 0;
    h[i][m + 1] = 0;
    st[0] = m + 1;
    for (int j = m; j >= 1; j--) {
        while (top > 0 && h[i][j] <= h[i][st[top]]) top--;
        R[j] = st[top] - 1;
        st[++top] = j;
    }
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        memset(h, 0,sizeof(h));
        for (int i = 1; i <= n; i++) {
            for (int j = 1, d; j <= m; j++) {
                scanf("%d", &d);
                h[i][j] = (d == 0 ? 0 : h[i - 1][j] + d);
            }
        }
//        for (int i = 1; i <= n; i++) {
//            for (int j = 1; j <= m; j++) {
//                printf("%d ", h[i][j]);
//            }
//            printf("\n");
//        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            get_LR(i);
            for (int j = 1; j <= m; j++) {
                ans = max(ans, h[i][j] * (R[j] - L[j] + 1));
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

H - The Fewest Coins( POJ3260 ) 这题的鸽笼原理确定上界有点懵

题意:

​ 有n种硬币,每种硬币分别有 c i c_i ci个,要买价值为T的物品,要使得自己支付的硬币数与卖家找回的硬币数之和最少

思路:

​ 多重背包和完全背包

dp_pay[MAXT + MAXV * MAXV]; //付钱为i时的最少硬币数

dp_change[MAXT + MAXV * MAXV];//找钱为i时的最少硬币数

所以 a n s = m i n ( d p p a y [ T + i ] + d p c h a n g e [ i ] ) ans = min(dp_pay[T + i]+dp_change[i]) ans=min(dppay[T+i]+dpchange[i])

因为找钱的时候硬币无限,所以一定是最优,完全背包

而付钱的时候硬币有限,求最小,用多重背包 (多重背包用二进制分解成01背包

要用鸽笼原理确定dp上界 (emmmm)

上界: MAX_T + MAX_V * MAX_V

鸽笼原理:

​ 意味着,要凑足(大于等于)价格T的商品且硬币数最少,最多只能多给max_v * max_v的金额(其中max_v为硬币的最大面值),称此上界为W。为什么会有这么紧的上界呢,假设存在一种最优支付方案,给了多于t + max_v * max_v的钱,那么商店就会找回多于max_v * max_v的钱,这些硬币的个数大于max_v。设这些硬币的面值分别为a_i,根据鸽笼原理的应用,硬币序列中存在至少两个子序列,这两个子序列的和分别都能被max_v整除。如果我们直接用长度更小的那个子序列换算为面值为max_v的硬币某整数个,再去替换母序列就能用更少的硬币买到商品,形成矛盾。

其实我还没搞懂。。。。。。只能意会一点点

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 110;
const int MAXT = 1e4 + 10;
const int MAXV = 120 + 1;
int max_v;
int n, T;
int v[N], c[N];
int dp_pay[MAXT + MAXV * MAXV];  //付钱为i时的最少硬币数
int dp_change[MAXT + MAXV * MAXV];  //找钱为i时的最少硬币数
//最终答案时min(dp_pay[i + T] + dp_change[i])
void dp_p(int n, int W) {  //W是背包容量
    memset(dp_pay, INF, sizeof(dp_pay));
    dp_pay[0] = 0;
    for (int i = 0; i < n; i++) {
        int num = c[i]; //当前硬币的最大数量
        for (int k = 1; num > 0; k <<= 1) { //二进制分解
            int cnt = min(k, num);
            for (int j = W; j >= v[i] * cnt; j--) {
                dp_pay[j] = min(dp_pay[j], dp_pay[j - v[i] * cnt] + cnt);
            }
            num -= cnt;
        }
    }
}
void dp_c(int n, int W) {
    memset(dp_change, INF, sizeof(dp_change));
    dp_change[0] = 0;
    for (int i = 0; i < n; i++) {
        for (int j = v[i]; j <= W; j++) {
            dp_change[j] = min(dp_change[j], dp_change[j - v[i]] + 1);
        }
    }
}
int main() {
    scanf("%d%d", &n, &T);
    max_v = 0;
    for (int i = 0; i < n; i++) {
        scanf("%d", &v[i]);
        max_v = max(max_v, v[i]);
    }
    for (int i = 0; i < n; i++)
        scanf("%d", &c[i]);

    //pay 多重背包(转换成01背包)
    dp_p(N, T + max_v * max_v);
    dp_c(N, T + max_v * max_v);
    int ans = INF;
    for (int i = max_v * max_v; i >= 0; i--) {
        ans = min(ans, dp_change[i] + dp_pay[T + i]);
    }
    if (ans == INF) {
        ans = -1;
    }
    printf("%d\n", ans);
    return 0;
}

Minimal Segment Cover(CF 1175E)

题意:

​ 给定n条[l, r] 的线段,给m次[l, r]的查询,每次求能覆盖该查询区间的最少线段数

思路:

​ 倍增

dp[i][j] 表示从i开始跳了 2 j 2^j 2j 次后能到达的最远距离

​ 因为,这个每次跳之间互不干扰,所以能倍增的得到,

从i跳了两次相当于从跳了一次的地方再跳了一次

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 5e5 + 10;
int n, m, a[N];
int dp[N][22]; //dp[i][j]表示从i开始跳了2^j步能达到的最远距离
int main() {
    scanf("%d%d", &n, &m);
    int l, r, mmax;
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &l, &r);
        a[l] = max(a[l], r);
        mmax = max(mmax, r);
    }
    for (int i = 1; i <= mmax; i++) a[i] = max(a[i], a[i - 1]);
    for (int i = 0; i <= mmax; i++) dp[i][0] = a[i];
    for (int j = 1; j <= 20; j++) {
        for (int i = 0; i <= mmax; i++) {
            dp[i][j] = dp[dp[i][j - 1]][j - 1]; //倍增,从i跳了两次相当于从跳了一次的地方再跳了一次
        }
    }
    while (m--) {
        int ans = 0;
        scanf("%d%d", &l, &r);
        for (int i = 20; i >= 0; i--) {
            if (dp[l][i] < r) ans += 1 << i, l = dp[l][i];
        }
        if (a[l] >= r) printf("%d\n", ans + 1);
        else printf("-1\n");
    }
    return 0;
}

玩具装箱(LOJ 10188)

题意:

​ 题意

​ 有n个物品,每个物品长度c[i]

​ 将物品分为连续的几组,每组内部每个物品用空间1隔开

​ 一组物品的长度为 i − j + ∑ i i < = j c [ i ] i - j + \sum_{i}^{i <=j}{c[i]} ij+ii<=jc[i]

​ 每组物品的价值为 ( 每组物品的长度 − L ) 2 (\text{每组物品的长度} - L)^2 (每组物品的长度L)2L为常数

思路:

dp + 斜率优化

dp[i] 表示放入了c[i] 后的最优分组的价值

img

在这里我们总结一下,单调队列斜率优化的步骤:
1.弹队头,就是最左边的点.
2.放直线,算答案,得到当前状态的答案,得到新的待加入的点. (当前斜率>最左端的斜率就弹跳队首)
3.弹队尾,把插入新点之后不合法的点弹掉.最后加入新点就好了. (加入当前这个点后,会破坏凸包的下凹性,就弹出队尾)

(以上只针对本题的dp方程)

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 50005;
int q[maxn];
double A[maxn], B[maxn], dp[maxn], sum[maxn];
double X(int x) {return B[x]; }
double Y(int x) {return dp[x] + B[x] * B[x]; }
double nk(int a, int b) {
    return (Y(a) - Y(b)) / (X(a) - X(b));
}

int main() {
    int n, l;
    scanf("%d%d", &n, &l);
    for (int i = 1; i <= n; i++)
        scanf("%lf", &sum[i]);
    for (int i = 1; i <= n; i++) {
        sum[i] += sum[i - 1];
        A[i] = sum[i] + i;
        B[i] = sum[i] + i + l + 1;
    }
    B[0] = l + 1; //B[0] = sum[0] + 0 + l + 1;
    int head = 1, tail = 1;
    for (int i = 1; i <= n; i++) {
        while (head < tail && nk(q[head], q[head + 1]) < 2 * A[i])
            head++;
        int j = q[head];
        dp[i] = dp[j] + (A[i] - B[j]) * (A[i] - B[j]);
        while (head < tail && nk(i, q[tail - 1]) <= nk(q[tail - 1], q[tail]))
            tail--;
        q[++tail] = i;
    }
    printf("%lld\n", (ll)dp[n]);
    return 0;
}

/*
5 4
3
4
2
1
4
*/
/*
1
*/

股票交易(计蒜客T2651)

题意:

​ T天内,每天的入股价为api, 出股价为bpi,每天至多入股asi,至多出股bsi

​ 手里的钱无限,手中的股票最多为maxp ,求最后一天的最大收益

思路: dp

dp[i][j] 表示第i天手中有j张股票的最大收益。

  • 第一次买股票 d p [ i ] [ j ] = − j ∗ a p i dp[i][j] = -j * api dp[i][j]=japi (0 <= j <= asi)
  • 什么都不干 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j]) dp[i][j]=max(dp[i][j],dp[i1][j])
  • 买股票 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − w − 1 ] [ k ] − ( j − k ) ∗ a p i ) dp[i][j] = max(dp[i][j], dp[i - w - 1][k] - (j - k) * api) dp[i][j]=max(dp[i][j],dp[iw1][k](jk)api) (i > w && j - asi <= k <= j)
  • 卖股票 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − w − 1 ] [ k ] + ( k − j ) ∗ b p i ) dp[i][j] = max(dp[i][j], dp[i - w - 1][k] + (k - j) * bpi) dp[i][j]=max(dp[i][j],dp[iw1][k]+(kj)bpi) (i > w && j <= k <= j + bsi)

前两种情况直接dp就行

后两种情况要用单调队列维护 d p [ i − w − 1 ] [ k ] + k ∗ a p [ i ] dp[i - w - 1][k] + k * ap[i] dp[iw1][k]+kap[i] d p [ i − w − 1 ] [ k ] + k ∗ b p [ i ] dp[i - w - 1][k] + k * bp[i] dp[iw1][k]+kbp[i]

​ (买)根据k的范围从小到大dp (卖)根据k的范围从大到小维护

具体看代码实现

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 50005;
int q[2001], l, r;
int dp[2001][2001];
int ap, bp, as, bs, mp, n, w, ans = 0;
int main () {
    
    scanf("%d%d%d", &n, &mp, &w);
    memset(dp, -INF, sizeof(dp));
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d%d", &ap, &bp, &as, &bs);
        for (int j = 0; j <= as; j++)  //第一次买股票
            dp[i][j] = -1 * j * ap;
        for (int j = 0; j <= mp; j++)  //啥都不干
            dp[i][j] = max(dp[i][j], dp[i - 1][j]);
        if (i <= w) continue;
        l = 1, r = 0; //单调队列清空
        for (int j = 0; j <= mp; j++) {  //买股票
            int down = j - as;
            while (l <= r && q[l] < down) l++;
            while (l <= r && dp[i - w - 1][q[r]] + q[r] * ap <= dp[i - w - 1][j] + j * ap)
                r--;
            q[++r] = j;
            if (l <= r)
                dp[i][j] = max(dp[i][j], dp[i - w - 1][q[l]] + q[l] * ap - j * ap);
        }
        l = 1, r = 0;
        for (int j = mp; j >= 0; j--) {
            int up = j + bs;
            while (l <= r && q[l] > up) l++;
            while (l <= r && dp[i - w - 1][q[r]] + q[r] * bp <= dp[i - w - 1][j] + j * bp)
                r--;
            q[++r] = j;
            if (l <= r)
                dp[i][j] = max(dp[i][j], dp[i - w - 1][q[l]] + q[l] * bp - j * bp);
        }
    }
    for (int i = 0; i <= mp; i++)
        ans = max(ans, dp[n][i]);
    printf("%d\n", ans);
    return 0;
}




 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Python中,单调单调队列是两种不同的数据结构。单调是一个,它的特点是内的元素是单调的,可以是递增或递减的。在构建单调时,元素的插入和弹出都是在的一端进行的。与此类似,单调队列也是一个队列,它的特点是队列内的元素是单调的,可以是递增或递减的。在构建单调队列时,元素的插入是在队列的一端进行的,而弹出则是选择队列头进行的。 单调队列在解决某些问题时,能够提升效率。例如,滑动窗口最大值问题可以通过使用单调队列来解决。单调队列的结构可以通过以下代码来实现: ```python class MQueue: def __init__(self): self.queue = [] def push(self, value): while self.queue and self.queue[-1 < value: self.queue.pop(-1) self.queue.append(value) def pop(self): if self.queue: return self.queue.pop(0) ``` 上述代码定义了一个名为MQueue的类,它包含一个列表作为队列的存储结构。该类有两个方法,push和pop。push方法用于向队列中插入元素,它会删除队列尾部小于插入元素的所有元素,并将插入元素添加到队列尾部。pop方法用于弹出队列的头部元素。 总结来说,单调单调队列都是为了解决特定问题而设计的数据结构。单调在构建时元素的插入和弹出都是在的一端进行的,而单调队列则是在队列的一端进行的。在Python中,可以通过自定义类来实现单调队列的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值