洛谷P4852单调队列优化动态规划

44 篇文章 0 订阅
41 篇文章 1 订阅
题目大意:

  

解题思路:
  • 题目所要求的是欧气之和最大,则等价于欧气损失最小,而欧气损失当且仅当在连抽时会有损失,所以我们设一个 b b b数组, b [ i ] = ∑ i i + c − 1 a [ i ] b[i]=\sum_{i}^{i+c-1}a[i] b[i]=ii+c1a[i],即代表如果连抽的开头选 i i i,那么损失就为 b [ i ] b[i] b[i]
  • 再观察题目条件,要求不能连续单抽d次,所以每相邻的连抽间隔(即前一次的最后与后一次的开头不能超过d,而且每次连抽之间不能互相影响
  • 题目通过上述条件就转换为:选择 n n n b i b_i bi,它们两两的下标之差大于等于 c c c 且小于等于 c + d c+d c+d,求它们的和的最小值
  • 就可以通过设 d p ( i , j ) dp(i,j) dp(i,j)为前i个选j个,且第 i i i次选了 b i b_i bi的最小值,来求解
  • d p dp dp方程为: d p ( i , j ) = m i n { d p ( k , j − 1 ) } + b [ i ] , k ∈ [ i − c − d , i − c ] dp(i,j)=min\{dp(k,j-1)\}+b[i],k\in [i-c-d,i-c] dp(i,j)=min{dp(k,j1)}+b[i],k[icd,ic],这个式子很明显就能利用单调队列来优化 d p dp dp
AC代码(内含注释):
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
typedef long long ll; 
int n, m, c, d, s, g, h, t;
int a[maxn], q[maxn], pre[maxn][50];
ll sum[maxn], b[maxn];
ll dp[maxn][50];
vector <int> ans;
int main() {
    cin >> n >> m >> c >> d;
    s = c * n + m;
    g = s - c + 1;
    for (int i = 1; i <= s; i++) cin >> a[i], sum[i] = sum[i - 1] + a[i]; 
    for (int i = 1; i <= g; i++) b[i] = sum[i + c - 1] - sum[i];
    memset(dp, 20, sizeof(dp));
    ll res = dp[0][0];
    for (int i = 1; i <= d + 1; i++) dp[i][1] = b[i]; //因为不能连续单抽d次
    for (int j = 2; j <= n; j++) {
        h = 1, t = 0;
        for (int i = 1; i <= g; i++) {
        	//不属于范围内,则要弹出
            while (h <= t && q[h] < i - c - d) h++;
            //注意非常重要的一点,因为是[i-c-d,i-c]所以对于i来说,新增的区间右端点是i-c,即要入队的是i-c而不是i
            while (h <= t && i - c > 0 && dp[i - c][j - 1] < dp[q[t]][j - 1]) t--;
            if (i - c > 0) q[++t] = i - c;
            if (h <= t && i - c - d <= q[h] && q[h] <= i - c) dp[i][j] = dp[q[h]][j - 1] + b[i], pre[i][j] = q[h];
            // q[++t] = i; //不应该这样压
            // int test_point = 0;
        }
    }
    int p = 0;
    for (int i = 1, j; i <= g; i++) {
        j = i + c - 1;
        if (res > dp[i][n] && s - j <= d) //之后的剩余数量不能超过d
            res = dp[i][n], p = i;
    }
    // cout << sum[s] << endl;
    // cout << res << endl;
    cout << sum[s] - res << endl;
    // for (int i = 1; i <= g; i++)
    //     cout << pre[i] << " \n"[i == g];
    int temp = n;
    while (p) ans.push_back(p), p = pre[p][temp--];
    reverse(ans.begin(), ans.end());
    for (auto np : ans) 
        cout << np << " ";
    cout << endl;
}
/*
2 3 2 1
11 12 19 12 2 11 11


3 3 2 1
16 1 13 9 20 18 12 4 10


4 4 2 1
16 20 20 4 14 11 9 19 20 7 6 6
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值