洛谷-3648 [APIO2014]序列分割

题目描述
你正在玩一个关于长度为 n 的非负整数序列的游戏。这个游戏中你需要把序列分成 k+1 个非空的块。为了得到 k+1 块,你需要重复下面的操作 k 次:
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
输入格式
第一行包含两个整数 n 和 k。保证 k+1≤n。
第二行包含 n 个非负整数 a 1 , a 2 , ⋯   , a n a_1, a_2, \cdots, a_n a1,a2,,an ( 0 ≤ a i ≤ 1 0 4 ) (0≤ai≤10^4) (0ai104),表示前文所述的序列。
输出格式
第一行输出你能获得的最大总得分。
第二行输出 k 个介于 1 到 n - 1 之间的整数,表示为了使得总得分最大,你每次操作中分开两个块的位置。第 i 个整数 s i s_i si​ 表示第 i 次操作将在 s i s_i si​ 和 s i + 1 s_{i + 1} si+1 之间把块分开。
如果有多种方案使得总得分最大,输出任意一种方案即可。

输入输出样例
输入 #1
7 3
4 1 3 4 0 2 3

输出 #1
108
1 3 5

说明/提示
2 ≤ n ≤ 100000 , 1 ≤ k ≤ m i n ⁡ { n − 1 , 200 } 2≤n≤100000,1≤k≤min⁡\{n−1,200\} 2n100000,1kmin{n1,200}

解释: d p [ k ] [ i ] : dp[k][i]: dp[k][i]:在i出化第k刀的最大值,则 d p [ k ] [ i ] = m a x { d p [ k − 1 ] [ j ] + ( s u m ( i ) − s u m ( j ) ) ∗ ( s u m ( n ) − s u m ( i ) ) } dp[k][i]=max\{dp[k-1][j]+(sum(i)-sum(j))*(sum(n)-sum(i))\} dp[k][i]=max{dp[k1][j]+(sum(i)sum(j))(sum(n)sum(i))}
我们枚举k,进行斜率优化就可以了,老套路
j > k j>k j>k,且 j j j优于 k k k,则最后可以化得 ( d p [ j ] − d p [ k ] ) / ( s u m [ j ] − s u m [ k ] ) > s u m [ n ] − s u m [ i ] (dp[j]-dp[k])/(sum[j]-sum[k])>sum[n]-sum[i] (dp[j]dp[k])/(sum[j]sum[k])>sum[n]sum[i],然后套斜率优化模板就好了。注意0要单独处理掉,0不会对答案最贡献,但可以出现斜率不存在的情况。

#include<cstring>
#include<iostream>
#define ll long long
#define N 100004
using namespace std;
int n=0,k=0;
ll sum[N]={0};
int q[N]={0};
ll dp[203][N]={0};
int front[203][N]={0};
int ans[203]={0};
int temp[N]={0};
int t=0;
double cal(int x,int y,int k){
    return 1.0*(dp[k][x]-dp[k][y])/(sum[x]-sum[y]);
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=1,a;i<=n;i++){
        cin>>a;
        if(a){
            ++t;
            sum[t]=sum[t-1]+a;
            temp[t]=i;
        }
    }
    n=t;t=0;
    for(int i=1;i<=k;i++){
        memset(dp[i],0,sizeof(dp[i]));
        q[1]=0;
        for(int j=1,l=1,r=1;j<=n;j++){
            while(l<r&&cal(q[l],q[l+1],i-1)>=1.0*(sum[n]-sum[j])) l++;
            dp[i][j]=dp[i-1][q[l]]+(sum[j]-sum[q[l]])*(sum[n]-sum[j]);
            front[i][j]=q[l];
            while(l<r&&cal(q[r],q[r-1],i-1)<=cal(q[r],j,i-1)) --r;
            q[++r]=j;
        }
    }
    int ret=0;
    for(int i=1;i<=n;i++) if(dp[k][ret]<=dp[k][i]) ret=i;
    ans[1]=ret;
    for(int i=2;i<=k;i++) ans[i]=front[k-i+2][ans[i-1]];
    cout<<dp[k][ret]<<endl;
    for(int i=k;i>=1;i--) cout<<temp[ans[i]]<<" ";
    cout<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值