题意
有一个长度为(n(1leq nleq 100000))的数列(a),将这个数列划分成几个子序列,每个子序列的长度不超过(L(1leq Lleq n)),并且每个子序列的最后一个元素严格单调递增。对于一种划分数为(k)的方案,设每个子序列的最后一个元素是(b[i](1leq ileq k)),(b[0]=0),则这个划分方案的得分为(sum (b[i]^2-b[i-1])),计算对于序列的合法划分所能得到的最大得分。
题解
设(f[i])表示以第(i)个元素结尾所能得到的最大得分,则(f[i]=max(f[j]+a[i]^2-a[j])),其中(i-Lleq jleq i-1)并且(a[j]<a[i])。
可以将(f[j]+a[i]^2-a[j])拆成((f[j]-a[j]))和(a[i]^2)两部分,其中(a[i]^2)由(i)本身决定,无法改变,((f[j]-a[j]))可以放进线段树查找,得到满足(i-Lleq jleq i-1)的最大值。
对于(a[j]<a[i])这个限制条件,可以通过更改计算顺序来实现。将(a[i])从小到大排序,逐个插入线段树中,这样线段树中的(a[j])全部小于当前计算的(a[i]),所以(a[i])一定是由比它小的(a[j])转移过来的。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn=100010;
int T,n,L;
LL tree[4*maxn],dp[maxn];
struct node{
int id;
LL x;
bool operator < (const node& t)const{
if(x!=t.x) return x<t.x;
return id>t.id;
}
}a[maxn];
void pushup(int o){
tree[o]=max(tree[o<<1],tree[o<<1|1]);
}
void build(int o,int l,int r){
if(l==r){
tree[o]=-1;
return;
}
int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushup(o);
}
void change(int o,int l,int r,int pos,LL v){
if(l==r){
tree[o]=v;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) change(o<<1,l,mid,pos,v);
else change(o<<1|1,mid+1,r,pos,v);
pushup(o);
}
LL query(int o,int l,int r,int ql,int qr){
if(ql<=l && r<=qr) return tree[o];
int mid=(l+r)>>1;
LL ans=-1;
if(ql<=mid) ans=max(ans,query(o<<1,l,mid,ql,qr));
if(qr>mid) ans=max(ans,query(o<<1|1,mid+1,r,ql,qr));
return ans;
}
int main(){
scanf("%d",&T);
for(int cas=1;cas<=T;cas++){
scanf("%d %d",&n,&L);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i].x);
a[i].id=i;
}
build(1,0,n);
change(1,0,n,0,0);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
dp[a[i].id]=-1;
LL t=query(1,0,n,max(0,a[i].id-L),a[i].id-1);
if(t==-1) continue;
dp[a[i].id]=t+a[i].x*a[i].x;
change(1,0,n,a[i].id,dp[a[i].id]-a[i].x);
}
printf("Case #%d: ",cas);
if(dp[n]==-1) printf("No solution
");
else printf("%lld
",dp[n]);
}
}