单调队列优化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;
}
此处有一个细节,就是第四个循环是倒序的,因为你的状态是从股票数比自己大的状态转移过来的,为了
保证每个状态都能被转移到,所以第四个循环要倒序