单调队列优化动态规划

今天是第一次写。本来打算抱着二分栈的写法不放,不想学别的写法,但是今天看到主席的代码是我的三分之一还不到,简直比裸的平方dp还要短,我就忍不住去学了。

先直接说这道题的方程:f[i] = max{f[j] + sum(j + 2, i)}, 1 <= i <= n, i - m - 1 <= j <= i - 1。

如果像我原来一样写二分栈的话,j 的限制将使代码变得很不方便,而且跑起来比单调队列慢一倍。

单调队列应该怎么写呢?

首先,把方程变形,得:f[i] - s[i] = max{f[j] - s[j + 1]}(限制条件如上,s[j] 表示前 j 项和)。

这样(很简单啊。。。)就得到了一个只与 j 有关的方程的另一边,记之为 X[j]。

然后如何处理呢?就可以通过维护一个单调队列,来保存所有当前合法的决策,其中从队首到队尾 X 值依次递减。每次通过 pop 队首保证决策的合法,然后用队首的最优决策更新当前的状态,然后每次在队尾加入当前所得的这个决策。加入时,为了维护队列的单调性,每次向前 pop 不如之优的决策。很显然,因为每次都会往队列中至少添加一个决策,而且每次决策前都会 pop 队首不合法决策,所以每次队列中的决策都一定会合法。而每个元素最多被加入队列一次,也最多被 pop 一次,所以整个过程的复杂度是 O(n) 的,完虐二分栈 O(nlogn)。

顺便说一下,由于觉得主席很酷,所以我决定也宏定义 for 循环。

Code :

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

typedef int TYPE;
typedef long long int64;
typedef unsigned int uint;
typedef unsigned long long uint64;

#define swap(a, b, t) ({t _ = (a); (a) = (b); (b) = _;})
#define MAX(a, b, t) ({t _ = (a), __ = (b); _ > __ ? _ : __;})
#define MIN(a, b, t) ({t _ = (a), __ = (b); _ < __ ? _ : __;})
#define max(a, b) ({TYPE _ = (a), __ = (b); _ > __ ? _ : __;})
#define min(a, b) ({TYPE _ = (a), __ = (b); _ < __ ? _ : __;})

#define FOR(i, l, r) for (int i = (l), _r = (r); i <= _r; ++ i)
#define ROF(i, r, l) for (int i = (r), _l = (l); i >= _l; -- i)
#define FER(u, e, v) for (int e = edge[u], v = to[e]; e; v = to[e = next[e]])

#define maxn 100005
#define front que[head + 1]
#define back que[tail]

int n, m, head, tail;
int a[maxn], que[maxn];
int64 s[maxn], f[maxn], k;

int main()
{
     freopen("cg.in", "r", stdin);
     freopen("cg.out", "w", stdout);
     
     scanf("%d%d", & n, & m);
     FOR(i, 1, n) scanf("%d", & a[i]), s[i] = s[i - 1] + a[i];
     que[++ tail] = - 1;
     FOR(i, 0, n)
     {
          while (head < tail && front < i - m - 1) ++ head;
          f[i] = f[front] + s[i] - s[front + 1], k = f[i] - s[i + 1];
          while (head < tail && k > f[back] - s[back + 1]) -- tail;
          que[++ tail] = i;
     }
     printf("%I64d\n", f[n]);
     
     return 0;
}

再帖我二分栈的代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <iostream>
#include <algorithm>

typedef long long TYPE;
typedef long long int64;
typedef unsigned int uint;
typedef unsigned long long uint64;

#define swap(a, b, t) ({t _ = (a); (a) = (b); (b) = _;})
#define MAX(a, b, t) ({t _ = (a), __ = (b); _ > __ ? _ : __;})
#define MIN(a, b, t) ({t _ = (a), __ = (b); _ < __ ? _ : __;})
#define max(a, b) ({TYPE _ = (a), __ = (b); _ > __ ? _ : __;})
#define min(a, b) ({TYPE _ = (a), __ = (b); _ < __ ? _ : __;})

#define maxn 100005
#define sum(i, j) (s[j] - s[(i) - 1])
//#define getit(i, j) ({int _ = i; f[_] + sum(_ + 2, j);})

int n, m, head, tail;
int64 a[maxn], s[maxn];
int64 ff[maxn], * f = ff + 1;
struct node{int l, r, k;} que[maxn];

void push(int l, int r, int k)
{
     while (tail and (que[tail].r = l - 1) < que[tail].l) -- tail;
     que[++ tail] = (node){l, r, k};
}

int pop_head()
{
     int ans = que[head + 1].k;
     if (++ que[head + 1].l > que[head + 1].r) ++ head;
     return ans;
}

void pop_tail()
{
     que[tail - 1].r = que[tail].r, -- tail;
     que[tail].r = min(que[tail].r, que[tail].k + m + 1);
}

int64 getit(int i, int j)
{
     return f[i] + sum(i + 2, j);
}

void update(int i)
{
     while (head < tail)
     {
          int l = max(que[tail].l, i + 2), r = que[tail].r, k = que[tail].k;
          if (getit(i, r) < getit(k, r)) break;
          if (getit(i, l) > getit(k, l))
               if (l == que[tail].l) {pop_tail(); continue;}
               else {que[tail].r = i + 1; break;}
          for (int mid = l + r >> 1; l < r; mid = l + r >> 1)
               getit(i, mid) < getit(k, mid) ? l = mid + 1 : r = mid;
          push(l, i + m + 1, i); break;
     }
     if (head == tail) push(i + 2, i + m + 1, i);
     else if (que[tail].r < i + m + 1) push(que[tail].r + 1, i + m + 1, i);
}

int main()
{
     freopen("cg.in", "r", stdin);
     freopen("cg.out", "w", stdout);
     
     scanf("%d%d", & n, & m);
     for (int i = 1; i <= n; ++ i)
          scanf("%I64d", & a[i]), s[i] = s[i - 1] + a[i];
     push(1, m, - 1), update(0);
     for (int i = 1; i <= n; ++ i)
     {
          int j = pop_head();
          f[i] = getit(j, i);
          update(i);
     }
     printf("%I64d\n", f[n]);
     
     return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值