APIO2014 序列分割

Link

Diffculty

普通:算法难度6,思维难度6,代码难度5

凸优化:算法难度7,思维难度7,代码难度8

Description

你正在玩一个关于长度为 n ​ n​ n 的非负整数序列的游戏。这个游戏中你需要把序列分成 k + 1 ​ k+1​ k+1 个非空的块。为了得到 k + 1 ​ k+1​ k+1 块,你需要重复下面的操作 k ​ k​ k 次:

  1. 选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
  2. 选择两个相邻元素把这个块从中间分开,得到两个非空的块。

每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分,输出一组方案。

1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ m i n ( n − 1 , 200 ) , 0 ≤ A i ≤ 1 0 4 1\le n\le 10^5,1\le k\le min(n-1,200),0\le A_i\le 10^4 1n105,1kmin(n1,200),0Ai104

Solution

首先可以证明答案只和切分点有关。

如果固定切分点之后,两两合并的答案一定是两两区间和的乘积之和。

那么我们可以设计出dp状态以及转移方程:

d p ( i , j ) dp(i,j) dp(i,j)代表前 i i i个位置,分了 j j j块,并且强制最后一块以 i i i结尾的最大价值。

我们令 s u m i sum_i sumi表示 A i A_i Ai的前缀和。

d p ( i , j ) = m a x 0 ≤ u &lt; i { d p ( u , j − 1 ) + s u m j × ( s u m i − s u m j ) } dp(i,j)=max_{0\le u&lt; i}\{dp(u,j-1)+sum_j\times (sum_i-sum_j)\} dp(i,j)=max0u<i{dp(u,j1)+sumj×(sumisumj)}

这个方程也是非常明显的,自己推一下就知道了。

显然可以拆式子,发现这东西可以斜率优化:

y = d p ( u , j − 1 ) − s u m j × s u m j , x = s u m j , k = − s u m i , y=dp(u,j-1)-sum_j\times sum_j,x=sum_j,k=-sum_i, y=dp(u,j1)sumj×sumj,x=sumj,k=sumi,要求最大化截距。

那么常规套路,直接记录 k k k个上凸壳就好了,并且单调队列维护一波。

只要再记录一波方案,就没什么问题了。

斜率优化的部分非常简单,大家应该都会做。

然而你会发现这题还是可以凸优化,感性理解它的答案是个上凸函数。

凸优化求答案的话,只需要注意一下记录最小次数的部分就好了,还有要判分母为0或者直接判0。

总而言之,只求答案的话,套路还是没什么变化的。

你可以在bzoj测试自己凸优化的答案是否正确。

我们现在考虑如何输出方案。

我们考虑凸优化的时候,多记录一个最大次数,同时记录所有最优值的转移前驱。

我们考虑每个点的可行次数都是一个区间,那么只要符合当前点的区间,之后也一定存在符合的区间。

那么我们相当于记录了一个决策图,我们只要从 n n n号点开始走,每次只走合法的,一定会走出一组合法方案的。

还要注意0对方案的影响,0会影响最大次数,也会影响方案输出,具体的数据你可以在UOJ看到。

我目前取得了BZOJ的rank3,洛谷的rank3,UOJ的rank2

爷稳稳tql

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#define LL long long
#define LD long double
using namespace std;
inline void read(int &x){
    char ch=' ';
    while(ch<'0' || ch>'9')ch=getchar();
    while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
const int N=1e5+5,K=205;
vector<int> c[N];
int n,k,a[N];
LL sum[N];
int q[N],L,R;
LL dp[N],mn[N],mx[N],mn2[N],mx2[N];
inline bool check(int a,int b,int c){
	LD k1=2e18,k2=2e18;
    if(sum[b]!=sum[a])k1=(LD)(dp[b]-sum[b]*sum[b]-dp[a]+sum[a]*sum[a])/(LD)(sum[b]-sum[a]);
	if(sum[c]!=sum[b])k2=(LD)(dp[c]-sum[c]*sum[c]-dp[b]+sum[b]*sum[b])/(LD)(sum[c]-sum[b]);
    if(k1<k2)return 1;
    else return 0;
}
inline bool check2(int a,int b,LL v){
    LD k1=(LD)(dp[b]-sum[b]*sum[b]-dp[a]+sum[a]*sum[a])/(LD)(sum[b]-sum[a]);
    if(k1>=v){
        if(fabs(k1-v)<1e-8)
            mn2[b]=min(mn2[a],mn2[b]),mx2[b]=max(mx2[a],mx2[b]);
        return 1;
    }
    else return 0;
}
inline void print(int pos,int times,int flag){
    while(pos && !a[pos] && mx[pos-1]>=times)pos--;
    if(!pos)
        return;
    times--;
    for(vector<int>::iterator it=c[pos].begin();it!=c[pos].end();++it){
        int x=*it;
        if(mn[x]<=times && times<=mx[x]){
            print(x,times,1);
            break;
        }
    }
    if(flag)printf("%d ",pos);
}
int main(){
    read(n);read(k);++k;
    for(int i=1;i<=n;++i)read(a[i]);
    for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
    LL l=0,r=sum[n]*sum[n],mid;
    while(l<r){
        mid=(l+r)>>1;
        L=1;R=0;
        for(int i=1;i<=n;++i){
            if(L>R){
                if(!a[i]){
                    dp[i]=dp[i-1];
                    mn[i]=mn[i-1];
                    mn2[i]=mn[i];
                }
                else{
                    dp[i]=-mid;
                    mn[i]=1;
                    mn2[i]=mn[i];
                    while(L<R && check(q[R-1],q[R],i))R--;
                    q[++R]=i;
                }
            }
            else{
                if(!a[i]){
                    dp[i]=dp[i-1];
                    mn[i]=mn[i-1];
                    mn2[i]=mn[i];
                }
                else{
                    mn2[q[L]]=mn[q[L]];
                    while(L<R && check2(q[L],q[L+1],-sum[i]))L++;
                    dp[i]=dp[q[L]]+sum[q[L]]*(sum[i]-sum[q[L]])-mid;
                    mn[i]=mn2[q[L]]+1;
                    mn2[i]=mn[i];
                    if(-mid>=dp[i]){
                        dp[i]=-mid;
                        mn[i]=mn2[i]=1;
                    }
                    while(L<R && check(q[R-1],q[R],i))R--;
                    q[++R]=i;
                }
            }
        }
        if(mn[n]>k)l=mid+1;
        else r=mid;
    }
    mid=l;
    L=1;R=0;
    for(int i=1;i<=n;++i){
        if(L>R){
            if(!a[i]){
                dp[i]=dp[i-1];
                mn[i]=mn[i-1];
                mn2[i]=mn[i];
				if(!mid)c[i].push_back(i-1),mx[i]=mx[i-1]+1;
				else mx[i]=mx[i-1];
                mx2[i]=mx[i];
				while(L<R && check(q[R-1],q[R],i))R--;
                q[++R]=i;
            }
            else{
                c[i].push_back(0);
                dp[i]=-mid;
                mn[i]=1;
                mn2[i]=mn[i];
                mx[i]=1;
                mx2[i]=mx[i];
                while(L<R && check(q[R-1],q[R],i))R--;
                q[++R]=i;
            }
        }
        else{
            if(!a[i]){
                dp[i]=dp[i-1];
                mn[i]=mn[i-1];
                mn2[i]=mn[i];
                if(!mid)c[i].push_back(i-1),mx[i]=mx[i-1]+1;
				else mx[i]=mx[i-1];
                mx2[i]=mx[i];
				while(L<R && check(q[R-1],q[R],i))R--;
                q[++R]=i;
            }
            else{
                mn2[q[L]]=mn[q[L]];
                mx2[q[L]]=mx[q[L]];
                while(L<R){
                    int a=q[L],b=q[L+1];
                    LD k1;
					if(sum[b]!=sum[a])k1=(LD)(dp[b]-sum[b]*sum[b]-dp[a]+sum[a]*sum[a])/(LD)(sum[b]-sum[a]);
					else k1=2e18;
                    if(k1>-sum[i]){
                        c[i].clear();
                        ++L;
                    }
                    else if(k1==-sum[i]){
                        mn2[b]=min(mn2[a],mn2[b]),mx2[b]=max(mx2[a],mx2[b]);
                        c[i].push_back(a);
                        ++L;
                    }
                    else break;
                }
                c[i].push_back(q[L]);
                dp[i]=dp[q[L]]+sum[q[L]]*(sum[i]-sum[q[L]])-mid;
                mn[i]=mn2[q[L]]+1;
                mx[i]=mx2[q[L]]+1;
                mn2[i]=mn[i];
                mx2[i]=mx[i];
                if(-mid>dp[i]){
                    dp[i]=-mid;
                    mx2[i]=mx[i]=mn[i]=mn2[i]=1;
                    c[i].clear();
                    c[i].push_back(0);
                }
				else if(-mid==dp[i])mn[i]=mn2[i]=1,c[i].push_back(0);
                while(L<R && check(q[R-1],q[R],i))R--;
                q[++R]=i;
            }
        }
    }
    printf("%lld\n",(long long)(dp[n]+k*mid));
    print(n,k,0);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值