送礼物
(gift.pas/c/cpp)
【问题描述】
Crash买来了件礼物,他要将这些礼物送给他的好(基)友们。
Crash先将礼物们排成一排,从左到右用正整数编号,每件礼物有一个正整数的价值,依次用表示。Crash送给每位好友的礼物一定是编号上连续的一段,满足这些礼物中任意两件礼物的价值差不会超过,同时送给每个好友的礼物个数不少于。
给出和每件礼物的价值,问Crash最多能送出多少件礼物。
【输入】
输入文件名为gift.in,共行,第一行包含一个正整数,表示有组输入需要求解。
下面每两行表示一组输入,第一行包含三个正整数。第二行包含个正整数,依次表示。
【输出】
输出文件名为gift.out,共行。第行对应第组输入的答案,包含一个非负整数,表示Crash最多能送出的礼物件数。
【输入输出样例】
gift.in gift.out
3
4 2 2
1 3 4 2
4 1 2
1 3 4 2
4 2 3
1 2 3 3 4
2
4
【样例说明】
样例中给出了三组输入。
第一组输入的方案是将礼物1、2送给好友1,礼物3、4送给好友2,这样总共能送出4件礼物。
第二组输入的方案是将礼物2、3送给好友1,这样总共能送出2件礼物。
第三组输入的方案是将礼物1、2、3、4送给好友1,这样总共能送出4件礼物。
【数据说明】n <= 200000
dp 还是太弱。。。第一反应看到这道题的转移限制很复杂,然后单调性感觉有两种不同的。。。。。总之就是写不出来。
其实还是单调队列。
首先看到方程,f[i] = min(f[i - 1], f[j] + i - j) 。显然,决策的优劣取决于 f[i] - i 。一个单调队列维护即可。
再者,转移限制一整段最大差小于 m 。有一种方法可以用单调队列维护区间最值:记一个单调上升的队列和一个单调下降的队列即可。
最后,转移限制必须从前 k 个之前转移。这要如何做呢? ccl 提供了一种很好的方法:在 i >= k 时向单调队列中插入决策 i - k, 这样就不会使决策被冲掉,保证了答案的正确性。
说来是很简单,实现也不是很复杂。但是以上两点非常重要。
Code :
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define swap(a, b, t) ({t _ = a; a = b; b = _;})
#define max(a, b) ({int _ = (a), __ = (b); _ > __ ? _ : __;})
#define min(a, b) ({int _ = (a), __ = (b); _ < __ ? _ : __;})
#define maxn 200005
int n, m, k, p;
int a[maxn], * aa, f[maxn];
char ss[maxn * 12], * ch;
struct que
{
int a[maxn], * head, * tail;
void clear() {head = tail = a;}
int & h() {return * (head + 1);}
int & t() {return * (tail);}
bool e() {return head == tail;}
void push(int s) {* (++ tail) = s;}
void poph() {++ head;}
void popt() {-- tail;}
} q, q1, q2;
void work()
{
memset(f, 0, sizeof f), p = 0;
q.clear(), q1.clear(), q2.clear();
for (int i = 1; i <= n; ++ i)
{
f[i] = f[i - 1];
while (! q1.e() && a[i] < a[q1.t()]) q1.popt();
while (! q2.e() && a[i] > a[q2.t()]) q2.popt();
q1.push(i), q2.push(i);
while (a[q2.h()] - a[q1.h()] > m)
{
++ p;
if (q1.h() <= p) q1.poph();
if (q2.h() <= p) q2.poph();
}
if (i >= k)
{
while (! q.e() && f[i - k] - (i - k) >= f[q.t()] - q.t()) q.popt();
q.push(i - k);
}
while (! q.e() && q.h() < p) q.poph();
if (! q.e()) f[i] = max(f[i], f[q.h()] + i - q.h());
}
printf("%d\n", f[n]);
}
void init()
{
scanf("%d%d%d\n", & n, & m, & k);
memset(a, 0, sizeof a), gets(ss);
for (aa = a + 1, ch = ss; * ch; ++ ch)
if ((* ch) == ' ') ++ aa;
else * aa = (* aa) * 10 + (* ch) - '0';
}
int main()
{
freopen("gift.in", "r", stdin);
freopen("gift.out", "w", stdout);
int t; scanf("%d", & t);
while (t --) init(), work();
return 0;
}