单调队列优化Dp

PTA-Little Bird

\(f[i]\)表示跳到\(i\)消耗体力最小值
则有\(f[i]= \begin{cases} min_{1<=j<=k} \ \ (f[i-j]);\ (d[i]>=d[i-j]) \\ min_{1<=j<=k} \ \ (f[i-j])+1; \ (d[i]<d[i-j]) \end{cases}\)
显然时间复杂度是\(O(qn^2)\)
那么怎么优化呢?
这时候就要想起我们的单调队列
维护一个单调下降的双端队列\(deq\)
\(f[i]\)作为\(deq\)的元素
每个元素都只进出单调队列一次,所以求出最大值的时间复杂度为\(O(1)\)
所以时间复杂度为\(O(qn)\)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e6+5;
int n,q,k,a[N];
int deq[N],f[N],head,tail;
int read() {
	char ch=getchar();
	int res=0,w=1;
	while(ch<'0'||ch>'9') {
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		res=res*10+ch-'0';
		ch=getchar();
	}
	return res*w;
}
int main() {
	n=read();
	for(register int i=1; i<=n; i++) {
		a[i]=read();
	}
	q=read();
	while(q--) {
		k=read();
		head=tail=1;
		deq[tail]=1;
		for(register int i=2; i<=n; i++) {
			while(head<=tail&&i-deq[head]>k)
				head++;
			if(a[i]<a[deq[head]])
				f[i]=f[deq[head]];
			else
				f[i]=f[deq[head]]+1;
			while(head<=tail&&((f[deq[tail]]>f[i])||
			                   (f[deq[tail]]==f[i]&&a[deq[tail]]<=a[i])))
				tail--;
			deq[++tail]=i;
		}
		printf("%d\n",f[n]);
	}
	return 0;
}

Watching Fireworks is Fun

\(f[i][j]\)表示放第\(i\)个烟花是你在第\(j\)个位置
不难列出方程
\(f[i][j]=max(f[i-1][k]+b[i]-\left \vert a[i]-x\right \vert)\\ max(j-d\times (t_i-t_{i-1}),0)\le k \le min(j+d \times (t_i-t_{i-1}),n)\)
时间复杂度大概\(O(nmd)\)
不难发现\(f[i-1][k]\)\(max\)值只与上一状态中连续的一段最大值有关,所以我们在计算一个新的i状态时可以将所有\(f[i-1]\)构造成一个单调队列,并维护单调队列,就可以在\(O(1)\)的时间内计算出\(max(f[i-1][k])\)的值,从而根据公式计算出\(f[i][j]\)的值,当然,\(f[i][j]\)数组的第一维也应改成滚动数组。
总的时间复杂度为\(O(nm)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=2e5+5;
ll n,m,d,t[N],f[2][N],flag,a[N];
ll deq[N*2],head,tail,ans,sum,b[N];
int read() {
	char ch=getchar();
	int res=0,w=1;
	while(ch<'0'||ch>'9') {
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		res=res*10+ch-'0';
		ch=getchar();
	}
	return res*w;
}
int main() {
	n=read(),m=read(),d=read();
	for(register int i=1; i<=m; i++) {
		a[i]=read();
		b[i]=read();
		t[i]=read();
		sum+=b[i];
	}
	memset(f,0x3f3f3f3f,sizeof(f));
	flag=1;
	for(register int i=1; i<=n; i++) f[1][i]=abs(a[1]-i);
	for(register int i=2; i<=m; i++) {
		head=1,tail=0;
		int now=i&1,last=i&1^1;
		memset(f[now],0x3f3f3f3f,sizeof(f[now]));
		for(register int j=1; j<=n; j++) {
			while(head<=tail&&deq[head]<j-d*(t[i]-t[i-1]))
				++head;
			while(head<=tail&&f[last][deq[tail]]>f[last][j])
				--tail;
			deq[++tail]=j;
			f[now][j]=min(f[now][j],f[last][deq[head]]+abs(a[i]-j));
		}
		head=1,tail=0;
		for(register int j=n; j>=1; j--) {
			while(head<=tail&&deq[head]>j+d*(t[i]-t[i-1]))
				++head;
			while(head<=tail&&f[last][deq[tail]]>f[last][j])
				--tail;
			deq[++tail]=j;
			f[now][j]=min(f[now][j],f[last][deq[head]]+abs(a[i]-j));
		}
		flag^=1;
	}
	ans=1e18/2;
	for(register int i=1; i<=n; i++) {
		ans=min(ans,f[m&1][i]);
	}
	printf("%lld\n",sum-ans);
	return 0;
}

[SCOI2010]股票交易

定义状态\(f[i][j]\)表示第i天持有j张股票能获得的最大利益
那么考虑以下几种转移方式

1、第i天不买也不卖

那么直接在i-1天的基础上转移过来即可
\(f[i][j]=max(f[i][j],f[i][j-1])\)

2、第i天只买股票

\(f[i][j]=-j\times ap[i] (j\le as_i)\)

3、第i天在以前的基础上买股票

\(f[i][j]=max(f[i-w-1][k]-ap_i\times (j-k));(j-as_i \le k \le j)\)

4、第i天在以前的基础上买股票

\(f[i][j]=max(f[i-w-1][k]+bp_i\times (k-j));(j \le k \le j+bs_i)\)
结果时间复杂度为\(O(t^3)\)
TLE定了
观察3,4的式子,它们很像,就拿4做例子
尝试把它拆开
\(f[i][j]=max(f[i-w-1][k]+bp_i\times k-bp_i\times j);(j \le k \le j+bs_i)\)
\(bp_i\times j\)可以说是一个定制,从\(max\)中提出
\(f[i][j]=max(f[i-w-1][k]+bp_i\times k)-bp_i\times j;(j \le k \le j+bs_i)\)
那我们也可以将\(f[i-w-1][k]+bp_i\times k\)扔进单调队列,然后维护单调队列就好了
三也可以如此转换

#include<bits/stdc++.h>
using namespace std;
const int N=2021;
int t,m,w;
int deq[N],as,bs,ap,bp,head,tail,f[N][N];
int main() {
	memset(f,128,sizeof(f));
	scanf("%d %d %d",&t,&m,&w);
	for(int i=1; i<=t; i++) {
		scanf("%d %d %d %d",&ap,&bp,&as,&bs);
		for(int j=0; j<=as; j++) {
			f[i][j]=-ap*j;
		}
		for(int j=0; j<=m; j++) {
			f[i][j]=max(f[i-1][j],f[i][j]);
		}
		if(i-w-1>0) {
			head=1,tail=0;
			for(int j=0; j<=m; j++) {
				while(head<=tail&&deq[head]<j-as)
					head++;
				while(head<=tail&&f[i-w-1][j]+ap*j>=f[i-w-1]
				        [deq[tail]]+ap*deq[tail])
					tail--;
				deq[++tail]=j;
				if(head<=tail)
					f[i][j]=max(f[i][j],f[i-w-1][deq[head]]+deq[head]*ap-j*ap);
			}
			head=1,tail=0;
			for(int j=m; j>=0; j--) {
				while(head<=tail&&deq[head]>j+bs)
					head++;
				while(head<=tail&&f[i-w-1][j]+bp*j>=f[i-w-1]
				        [deq[tail]]+bp*deq[tail])
					tail--;
				deq[++tail]=j;
				if(head<=tail)
					f[i][j]=max(f[i][j],f[i-w-1][deq[head]]+deq[head]*bp-j*bp);
			}
		}
	}
	int ans=INT_MIN;
	for(int i=0; i<=m; i++) {
		ans=max(ans,f[t][i]);
	}
	printf("%d\n",ans);
	return 0;
}

此处有一个细节,就是第四个循环是倒序的,因为你的状态是从股票数比自己大的状态转移过来的,为了
保证每个状态都能被转移到,所以第四个循环要倒序

posted @ 2021-07-02 13:04  redproblemdog  阅读(55)  评论(0编辑  收藏  举报