给定两个正整数 和一个长度为 的整数序列 ,你需要进行任意次下列操作把所有 变成 :
- 选择一段长度为 的区间 和一个正整数 ,把 中所有小于等于 的数改为 ;
最小化所有操作的 的和。
对于每一个 的前缀 ,你要求出它的答案 ,输出 。
,,时间复杂度要求线性。
原题 时限 放了 过。
考虑一个朴素的 dp,设 为 的答案。由于每次操作可以看作花费 的代价把满足 的 变成 ,所以转移可以考虑枚举把 变成 的那次操作的 :
显然 单调递增,所以只统计满足 的 的最小值即可。
这样的 可以很方便地用单调队列维护,但是发现由于单调队列中既会加入会删除, 只能 维护。
考虑优化,发现满足 的 才对 有贡献,所以考虑按 分块(、 等等为一块)。设 为合法的 构成的集合,则:
- 贡献可以分为块内贡献和块间( 所在块和上一块)贡献;
- 块内贡献:正着扫, 中只会加入新元素;
- 块间贡献:倒着扫, 中同样只会加入新元素;
注意要先处理块间贡献。
时间复杂度 ,实测 只跑了 ,不知道为什么原题不开 时限 。
还可以双栈。
代码如下:
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
return x*f;
}
const int S=10000005,p=1000000007;
const long long inf=1e17;
int n,k,a[S],_23[S];
long long f[S];
int top,sta[S];
long long stb[S];
int mx[S],tot,mxp[S];
int main()
{
freopen("op.in","r",stdin);
freopen("op.out","w",stdout);
n=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read();
_23[0]=1;
for(int i=1;i<=n;i++) _23[i]=1ll*_23[i-1]*23%p;
#define R(x) (min(n,((x-1)/k+1)*k))
f[0]=0;
for(int i=1;i<=n;i++) f[i]=inf;
for(int l=1;l<=n;l=R(l)+1)
{
int r=R(l);
// 上一块 - 当前块
if(l>1)
{
int l2=l-k,r2=l-1;
mx[l-1]=0;
for(int i=l;i<=r;i++) mx[i]=max(mx[i-1],a[i]);
tot=0;
for(int i=r2,mx=0;i>=l2;i--) if(a[i]>mx) mx=a[i],mxp[++tot]=i;
long long mn=inf;
for(int i=r,lb=1,rb=-1;i>=l;i--)
{
int p=i-k;
while(lb<=tot&&mxp[lb]>=p)
{
if(mx[i]<=a[mxp[lb]])
{
if(rb==-1) rb=lb;
else mn=min(mn,f[mxp[lb]]+a[mxp[lb-1]]);
}
lb++;
}
if(rb==-1&&mx[i]<=a[mxp[lb-1]]) rb=lb-1;
if(rb!=-1)
{
while(rb-1<=tot&&a[mxp[rb-1]]>=mx[i])
{
mn=min(mn,f[mxp[rb]]+a[mxp[rb-1]]);
rb--;
}
}
f[i]=min(f[i],min(mn,min(
f[p]+max(a[mxp[lb-1]],mx[i]),
rb!=-1?f[mxp[rb]]+mx[i]:inf
)));
}
}
// 当前块内
top=0,sta[0]=l-1,stb[0]=inf;
for(int i=l;i<=r;i++)
{
while(top>0&&a[sta[top]]<a[i]) top--;
top++;
sta[top]=i,stb[top]=min(stb[top-1],f[sta[top-1]]+a[i]);
f[i]=min(f[i],stb[top]);
}
}
int ans=0;
for(int i=1;i<=n;i++) ans=(ans+1ll*f[i]%p*_23[n-i]%p)%p;
printf("%d\n",ans);
return 0;
}