洛谷P4852
设\(f[i][j][0/1]\)表示用了\(i\)次连抽,\(j\)次单抽且结尾是连抽/单抽的最大价值
\(f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1])+a_{i*c+j-c+1}\)
\(f[i][j][1]=max(f[i][j-k][0]+sum_{i*c+j}-sum_{i*c+j-k})\)
其中\(1<=k<=d\),\(sum\)是前缀和数组
这两个方程浅显易懂,读者很容易模拟出来,就不再赘述了
直接进入正题:用单调队列优化此方程
第一个方程显然是\(O(1)\)的,不用优化
对于第二个方程,改写成
\(f[i][j][1]=max(f[i][j-k][0]-sum_{i*c+j-k})+sum_{i*c+j}\)
容易发现这是单调队列优化的经典形式,就做完了。
时间复杂度\(O(nm)\)
实际上这种分离变量使不同的变量相互隔开的做法非常常见,建议读者在做一下这道题,感受方程是如何变化的。
\(code\)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=45,M=80010,C=3010;
int n,m,c,d;
int a[N*C+M],sum[N*C+M];
struct Node
{
int pos,val;
}temp,q[M];
struct node
{
int x,y,z;
}pre[N][M][2];
ll f[N][M][2];
int read()
{
int x=0,f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-48;s=getchar();}
return x*f;
}
void print(int x,int y,int z)
{
if(!x) return;
print(pre[x][y][z].x,pre[x][y][z].y,pre[x][y][z].z);
if(!z) printf("%d ",x*c+y-c+1);
}
int main()
{
n=read(),m=read(),c=read(),d=read();
for(int i=1;i<=c*n+m;i++)
{
a[i]=read();
sum[i]=sum[i-1]+a[i];
}
memset(f,-0x3f,sizeof(f));
for(int i=1;i<=d;i++) f[0][i][1]=sum[i];
for(int i=1;i<=n;i++)
for(int j=0,l=1,r=0;j<=m;j++)
{
if(i>0)
{
if(i==1&&j==0) f[i][j][0]=a[1];
else f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1])+a[(i-1)*c+j+1];
pre[i][j][0].x=i-1,pre[i][j][0].y=j;
if(f[i-1][j][0]>f[i-1][j][1]) pre[i][j][0].z=0;
else pre[i][j][0].z=1;
}
if(j>0)
{
while(l<=r&&q[l].pos<j-d) l++;
if(l<=r) f[i][j][1]=q[l].val+sum[i*c+j];
pre[i][j][1].x=i,pre[i][j][1].y=q[l].pos,pre[i][j][1].z=0;
temp.pos=j,temp.val=f[i][j][0]-sum[i*c+j];
while(l<=r&&q[r].val<=temp.val) r--;
q[++r]=temp;
}
else
{
temp.pos=0,temp.val=f[i][0][0]-sum[i*c];
q[++r]=temp;
}
}
printf("%lld\n",max(f[n][m][0],f[n][m][1]));
if(f[n][m][0]>f[n][m][1]) print(n,m,0);
else print(n,m,1);
return 0;
}
另外一个小插曲,我数组\(a\)和\(sum\)的大小一开始开的\(N\)然后乱WA(是的,并不是RE),直接导致我多调3小时。wtcl
谢谢阅读