[单调队列] gift 送礼物

送礼物
(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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值