hdu4719 线段树优化dp
题意
有一个长度为\(n(1\leq n\leq 100000)\)的数列\(a\),将这个数列划分成几个子序列,每个子序列的长度不超过\(L(1\leq L\leq n)\),并且每个子序列的最后一个元素严格单调递增。对于一种划分数为\(k\)的方案,设每个子序列的最后一个元素是\(b[i](1\leq i\leq 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-L\leq j\leq 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-L\leq j\leq 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\n");
else printf("%lld\n",dp[n]);
}
}