【HDU4719】Oh My Holy FFF(线段树优化DP)

【HDU4719】Oh My Holy FFF
这题面还挺有意思的

题目大意

n n n个士兵排成一行,现在要在不打乱顺序的前提下把他们分成若干组,每组士兵数量不能大于 L L L,且每一组最右边的士兵要高于前一组最右边的士兵。每种分组方案都有一个相应的分数,分数的计算方式为:从第二组开始, s c o r e = ∑ ( h i 2 − h i − 1 ) score=\sum(h_i^2-h_{i-1}) score=(hi2hi1)。其中 h i h_i hi表示第 i i i组最右边的士兵的身高。求最高的分数。

题解

前置知识:线段树优化DP
f i f_i fi表示将前 i i i个士兵分成若干组所能获得的最大分数。则转移式为:

f i = max ⁡ ( f j + h i 2 − h j ) f_i=\max(f_j+h_i^2-h_j) fi=max(fj+hi2hj),其中 i − l e n ≤ j ≤ i − 1 i-len\leq j\leq i-1 ilenji1,且 h j < h i h_j<h_i hj<hi

h i 2 h_i^2 hi2提出,得 f i = h i 2 + max ⁡ ( f j − h j ) f_i=h_i^2+\max(f_j-h_j) fi=hi2+max(fjhj)

于是,我们可以用线段树来维护 f j − h j f_j-h_j fjhj的最大值。但是,怎么保证 h j < h i h_j<h_i hj<hi呢?

我们可以调整枚举 i i i的顺序,按 h i h_i hi从小到大来枚举 i i i。如果两个士兵 i i i值相同,则先处理编号大的,避免被 h h h值相同的更新。求 f i f_i fi时,在线线段树上查找对应段的最大值。求出 f i f_i fi后,更新线段树上相应位置的值。因为是按 h i h_i hi从小到大枚举的,所以不会存在 h h h值大的更新 h h h值小的。
总时间复杂度为 O ( n l o n g n ) O(nlongn) O(nlongn)

code

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define lc k<<1
#define rc k<<1|1
using namespace std;
int t,n,l;
long long sum,inf=1e18,f[100005],tr[400005];
struct node{
    long long x;
    int id;
}a[100005];
bool cmp(node ax,node bx){
    if(ax.x==bx.x) return ax.id>bx.id;
    return ax.x<bx.x;
}
void build(int k,int l,int r){
    tr[k]=-inf;
    if(l==r) return;
    int mid=(l+r)/2;
    build(lc,l,mid);
    build(rc,mid+1,r);
}
void ch(int k,int l,int r,int x,long long y){
    if(l==r&&l==x){
        tr[k]=y;
        return;
    }
    if(r<x||l>x) return;
    if(l==r) return;
    int mid=(l+r)/2;
    if(x<=mid) ch(lc,l,mid,x,y);
    else ch(rc,mid+1,r,x,y);
    tr[k]=max(tr[lc],tr[rc]);
}
void find(int k,int l,int r,int x,int y){
    if(x>y) return;
    if(l>=x&&r<=y){
        sum=max(sum,tr[k]);
        return;
    }
    if(r<x||l>y) return;
    if(l==r) return;
    int mid=(l+r)/2;
    if(x<=mid) find(lc,l,mid,x,y);
    if(y>mid) find(rc,mid+1,r,x,y);
}
int main()
{
    scanf("%d",&t);
    for(int o=1;o<=t;o++){
        scanf("%d%d",&n,&l);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i].x);
            a[i].id=i;
            f[i]=-inf;
        }
        build(1,1,n);
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++){
            if(a[i].id<=l) f[i]=a[i].x*a[i].x;
            sum=-inf;
            find(1,1,n,max(a[i].id-l,1),a[i].id-1);
            if(sum>-inf) f[i]=max(f[i],sum+a[i].x*a[i].x);
            if(f[i]>-inf) ch(1,1,n,a[i].id,f[i]-a[i].x);
            if(a[i].id==n){
                if(f[i]==-inf) printf("Case #%d: No solution\n",o);
                else printf("Case #%d: %lld\n",o,f[i]);
                break;
            }
        }
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值