【APIO2014】序列分割

本文介绍了一种解决石子合并问题的动态规划方法,通过优化得出结论:分割点相同的情况下,分割顺序无关紧要。文章给出了具体的DP方程,并通过斜率优化降低复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

这里写图片描述

Solution

怎么做

发现长得好像什么石子合并之内的题目,然后探究探究,得出了一个结论:分割点相同,分割的顺序是没有关系的。
然后就想到了DP。设f[i][j]表示在第i次分割时,在j点分割。
转移引进一个x就可以了f[i][j]=max(f[x][j-1]+sum[x]*(sum[i]-sum[x])),打个前缀和,很显然。
复杂度(O(n2k))

斜率优化

发现DP的有一维其实是没有什么用的,可以打成滚动数组,然后就只剩下一个有用维度了,一般只用一维的DP都可以打斜率优化。
打完斜率优化得到个这个东西:

f[x]s[x]2f[y]+s[y]2s[y]s[x]>s[i]

然后用单调队列存储就可以了。

Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define fo(i,a,b) for(i=a;i<=b;i++)
typedef long long ll;
using namespace std;
const int maxn=100007;
int i,j,k,t,n,m,x,v;
int g[maxn][207];
int d[maxn],l,r,ans[maxn];
ll a[maxn],sum[maxn],f[2][maxn];
ll mu(int x,int y,int i){
    return f[i][x]-sum[x]*sum[x]-f[i][y]+sum[y]*sum[y];
}
ll zi(int x,int y){
    return sum[y]-sum[x];
}
int main(){
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n)scanf("%lld",&a[i]),sum[i]+=sum[i-1]+a[i];
    fo(j,1,m){
        l=1,r=0,d[l]=0,v^=1;
        fo(i,1,n){
            while(l<r&&mu(d[l],d[l+1],v^1)<=zi(d[l],d[l+1])*sum[i])l++;
            f[v][i]=f[v^1][d[l]]+sum[d[l]]*(sum[i]-sum[d[l]]);
            g[i][j]=d[l];
            while(r>l&&mu(d[r-1],d[r],v^1)*zi(d[r],i)>=zi(d[r-1],d[r])*mu(d[r],i,v^1))r--;
            d[++r]=i;
        }
    }
    printf("%lld\n",f[v][n]);
    j=0;
    for(i=g[n][m];i;i=g[i][m-j]){
        j++;ans[j]=i;
    }
    fo(i,1,m)printf("%d ",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值