单调队列两则

单调队列
对于求解
f[x] = max or min {g(k) | b[k] <= k < x} + w[x]
(其中b[x]随x具有单调性,b[1] <= b[2] <= b[3] <= ... <= b[n])
如果是max()
可发现 如果存在两个数,i,j,i <= j,g[j] >= g[i] 则决策j是毫无用处的。
因为根据单调性如果i可作为合法决策,则j一定可做为合法决策。


hdu 3401
dp[i][j] 第i天有j股票
dp[i][j] = max(dp[i-k][j-h] - h*ap[i],dp[i-k][j+h] + h*bp[i]);


对于买的情况
dp[i][j] = max(dp[i-w-1][k] - (j-k)*ap[i])
dp[i][j] + j*ap[i] = max(dp[i-w-1][k]+k*ap[i])
对f(k) = dp[i-w-1][k] + k*ap[i];
于 i,j,找出最优的k使得f(k) 最大
[j-as[i],j] 区间向右移
又因为dp[i][] = max(dp[i][],dp[i-1][]), 优先性是可以继承的。
队列里元素满足b[k1] > b[k2] > b[k2] > ..且 k1 < k2 < k3 < k4....
f(k) = dp[i-w-1][k] + k*ap[i]
max(f(k)) (j-as[i] <= k <= j)


对于卖的情况
dp[i][j] = max(dp[i-w-1][k] + (k-j)*bp[i])
dp[i][j] + j*bp[i] = max(dp[i-w-1][k] + k*bp[i])
[j,j+bs[i]] 向左移
b[ki] > b[kj] && ki > kj;


hdu 4258


这题是到相当不错的单调队列题目
题意是在一个二维坐标系上有n个点,要求你覆盖这些点,覆盖一个区间的花费是区间长度的平方加上一个常数c,问最小花费。(一个点看作长度为0的区间)
显然我们要涂的区间左右边界必定是要涂的点。很容易想到一种朴素的dp, dp[i] = min(dp[j-1] + (X[i]-X[j])^2 + c) 1 <= j <= i
这个思路显然是好的,然后就是怎么优化的问题了。根据经验,我们把平方项展开,dp[j-1] + X[i]^2 + X[j]^2 - 2*X[i]*X[j],对于 j < K <= i,令f(i,j) = dp[j-1] + X[i]^2 + X[j]^2 - 2*X[i]*X[j],如果k比j更优,即f(i,j) > f(i,k),很容易发现
定理1:任意j < k <= i < u,如果f(i,k) <= f(i,j),则f(u,k) <= f(u,j).
光有定理1我们无法利用单调队列,因为还有f(i,k) > f(i,j)的情况。
我们接着会发现,第一点,随着u越来越大,-2*u*x[k]会越来越小,而且减小的速度大于-2*u*x[j],就可能出现f(u,k) <= f(u,j).我们可以设一个变量t,使得f(t,k)恰好小于等于f(t,j),用T(j,k)表示t,只有u >= T(j,k)的时候,k才有可能成为最优解。
第二点,对于j < k < i < u,满足f(u,j) < f(u,k) ,f(u,k) < f(u,i),但是如果f(T(j,k),i) <= f(T(j,k),k),就表示在k成为最优解之前i已经比它更优了。所以此时对于任意v > T(j,k),上述j,k,i的关系是不成立的。反之它则是成立的。
定理2:  对于j < k < i < u,如果f(u,j) < f(u,k) ,f(u,k) < f(u,i),且f(T(j,k),i) <= f(T(j,k),k)。k永远不可能成为最优解。
这样有了定理1,2我们就可以通过维护一个单调队列,来优美的解决这题了。

这个单调队列满足对于第j号元素和第j号元素,j的点坐标小于k的点坐标,且j比k更优,且对于j > k > i ,f(T(j,k),k) < f(T(j,k),i)

贴一下这题代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <cmath>
using namespace std;

#define pf(x) printf("%d\n",x)
#define pf2(x,y) printf("%d %d\n",x,y)
#define pf3(x,y,z) printf("%d %d %d\n",x,y,z)
#define pf4(x,y,z,k)printf("%d %d %d %d\n",x,y,z,k)
#define sf(x) scanf("%d",&x)
#define sf2(x,y) scanf("%d %d",&x,&y)
#define sf3(x,y,z) scanf("%d %d %d",&x,&y,&z)

typedef long long ll;
double const eps = 1e-6;
const int inf = 0x3fffffff;
const int size = 1000000 + 5;
ll X[size],dp[size];
int q[size];
inline ll cal(ll x,ll j)
{
	return dp[j-1] + X[j]*X[j] - 2*x*X[j];
}
//dp[j-1] + X[j]*X[j] - 2*X[i]*X[j] = dp[k-1] + X[k]*X[k] - 2*X[i]*X[k]
//
inline ll TL(ll j,ll k)
{
	ll a = (dp[j-1]+X[j]*X[j] - (dp[k-1]+X[k]*X[k])) / (2*X[j]-2*X[k]);
	ll b = (dp[j-1]+X[j]*X[j] - (dp[k-1]+X[k]*X[k])) % (2*X[j]-2*X[k]);
	return a + (b != 0);
}

int main()
{
	int n,c;
	//freopen("covered.in","r",stdin);
	while(cin >> n >> c && (n || c)){
		for(int i = 1;i <= n ; i++)
			cin >> X[i];
		int f = 0,r = -1;
		dp[0] = 0;
		for(int i = 1; i <= n; i++){
			while(r >= f && cal(X[i],q[r]) >= cal(X[i],i))
				r--;
			while(r - f >= 1){
				int tmp = TL(q[r],q[r-1]);
				if(cal(tmp,q[r]) >= cal(tmp,i))
					r--;
				else break;
			}
			q[++r] = i;
			while(f < r && TL(q[f],q[f+1]) <= X[i])
				f++;
			dp[i] = X[i]*X[i] + cal(X[i],q[f]) + c;
		}
		cout << dp[n] << endl;
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值