Luogu P3648 [APIO2014]序列分割

题目大意

给定一个长度为 n n n 的序列,你需要将其切割成大小任意的 k k k 段,设切割的位置为 ( i , i + 1 ) (i,i+1) (i,i+1),则该次切割的价值为 i i i 位置所在段的大小乘上 i + 1 i+1 i+1 位置所在段的大小,求最大的价值,并输出任意一种切割方法。(Special Judge)
数据范围  2 ⩽ n ⩽ 1 0 5 , 1 ⩽ k ⩽ m i n ( n − 1 , 200 ) 2\leqslant n\leqslant 10^5,1\leqslant k\leqslant min(n−1,200) 2n105,1kmin(n1,200)

题解

手玩几次就能发现,答案和分割顺序无关,下面是证明。
如果我们有长度为 3 的序列 x , y , z x,y,z x,y,z,将其切割为 3 3 3 段,有两种分割方法:

  1. 先在 x x x 后面分割,答案为 x ( y + z ) + y z x(y+z)+yz x(y+z)+yz 即为 x y + y z + z x xy+yz+zx xy+yz+zx
  2. 先在 y y y 后面分割,答案为 ( x + y ) z + x y (x+y)z+xy (x+y)z+xy 即为 x y + y z + z x xy+yz+zx xy+yz+zx

然后再将序列扩到任意长度即可,证明完毕。

既然分割的顺序不影响答案,那么就能很容易想到动态规划。
定义: f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个数切割成 j j j 段能得到的最大价值, s u m [ n ] = ∑ i = 1 n a [ i ] sum[n]=\sum\limits_{i=1}^na[i] sum[n]=i=1na[i]
转移: f [ i ] [ j ] = max ⁡ k = 1 i − 1 f [ k ] [ j − 1 ] + s u m [ k ] ∗ ( s u m [ i ] − s u m [ k ] ) f[i][j]=\max\limits_{k=1}^{i-1} f[k][j-1]+sum[k]*(sum[i]-sum[k]) f[i][j]=k=1maxi1f[k][j1]+sum[k](sum[i]sum[k])

int cur=0;
for(int j=1;j<=k;++j,cur^=1)
	for(int i=1;i<=n;i++)
		for(int k=1;k<i;k++)
			f[cur][i]=max(f[cur][i],f[cur^1][k]+sum[k]*(sum[i]-sum[k]));

这样时间复杂度为 Θ ( n 2 k ) \Theta(n^2k) Θ(n2k),但可以发现时间炸了。

下面大胆地假设我们没有学过斜率优化

考虑优化,在转移的时候,考虑两个点 k a k_a ka k b k_b kb 满足 k a &lt; k b k_a&lt;k_b ka<kb,若 k a k_a ka 优于 k b k_b kb,则有 f [ k a ] [ j − 1 ] + s u m [ k a ] ∗ ( s u m [ i ] − s u m [ k a ] ) &gt; f [ k b ] [ j − 1 ] + s u m [ k b ] ∗ ( s u m [ i ] − s u m [ k b ] ) f[k_a][j-1]+sum[k_a]*(sum[i]-sum[k_a])&gt;f[k_b][j-1]+sum[k_b]*(sum[i]-sum[k_b]) f[ka][j1]+sum[ka](sum[i]sum[ka])>f[kb][j1]+sum[kb](sum[i]sum[kb])

考虑化简,得 f [ k a ] [ j − 1 ] + s u m [ k a ] ∗ s u m [ i ] − s u m [ k a ] 2 &gt; f [ k b ] [ j − 1 ] + s u m [ k b ] ∗ s u m [ i ] − s u m [ k b ] 2 f[k_a][j-1]+sum[k_a]*sum[i]-sum[k_a]^2&gt;f[k_b][j-1]+sum[k_b]*sum[i]-sum[k_b]^2 f[ka][j1]+sum[ka]sum[i]sum[ka]2>f[kb][j1]+sum[kb]sum[i]sum[kb]2

移项,合并同类项
f [ k a ] [ j − 1 ] − f [ k b ] [ j − 1 ] + s u m [ k b ] 2 − s u m [ k a ] 2 &gt; ( s u m [ k b ] − s u m [ k a ] ) ∗ s u m [ i ] f[k_a][j-1]-f[k_b][j-1]+sum[k_b]^2-sum[k_a]^2&gt;(sum[k_b]-sum[k_a])*sum[i] f[ka][j1]f[kb][j1]+sum[kb]2sum[ka]2>(sum[kb]sum[ka])sum[i]

两边同时除以 s u m [ i ] &gt; 0 sum[i]&gt;0 sum[i]>0,得
f [ k a ] [ j − 1 ] − f [ k b ] [ j − 1 ] + s u m [ k b ] 2 − s u m [ k a ] 2 s u m [ k b ] − s u m [ k a ] &gt; s u m [ i ] \frac {f[k_a][j-1]-f[k_b][j-1]+sum[k_b]^2-sum[k_a]^2}{sum[k_b]-sum[k_a]}&gt;sum[i] sum[kb]sum[ka]f[ka][j1]f[kb][j1]+sum[kb]2sum[ka]2>sum[i]

再整理一下 ( f [ k a ] [ j − 1 ] − s u m [ k a ] 2 ) − ( f [ k b ] [ j − 1 ] − s u m [ k b ] 2 ) s u m [ k b ] − s u m [ k a ] &gt; s u m [ i ] \frac {(f[k_a][j-1]-sum[k_a]^2)-(f[k_b][j-1]-sum[k_b]^2)}{sum[k_b]-sum[k_a]}&gt;sum[i] sum[kb]sum[ka](f[ka][j1]sum[ka]2)(f[kb][j1]sum[kb]2)>sum[i]

因此,对于 k a k_a ka 来说 k a k_a ka 优于 k b k_b kb 当且仅当上面的式子成立。

接下来是跳跃的一步骤,对于每个 k t k_t kt,在平面直角坐标系中绘制点
K t ( s u m [ k t ] , f [ k t ] [ j − 1 ] − s u m [ k t ] 2 ) K_t(sum[k_t],f[k_t][j-1]-sum[k_t]^2) Kt(sum[kt],f[kt][j1]sum[kt]2),则
s p o l e a , b = ( f [ k a ] [ j − 1 ] − s u m [ k a ] 2 ) − ( f [ k b ] [ j − 1 ] − s u m [ k b ] 2 ) s u m [ k b ] − s u m [ k a ] spole_{a,b}=\dfrac {(f[k_a][j-1]-sum[k_a]^2)-(f[k_b][j-1]-sum[k_b]^2)}{sum[k_b]-sum[k_a]} spolea,b=sum[kb]sum[ka](f[ka][j1]sum[ka]2)(f[kb][j1]sum[kb]2)恰好为直线 K a K b K_aK_b KaKb 的斜率,如下图,直线 K 1 K 2 K_1K_2 K1K2 的斜率恰好就是 s p o l e 1 , 2 spole_{1,2} spole1,2
AybLp4.png
至此,我们得到了一条结论,当且仅当 k a &lt; k b k_a&lt;k_b ka<kb s p o l e a , b &gt; s u m [ i ] spole_{a,b}&gt;sum[i] spolea,b>sum[i] 时, k a k_a ka 优于 k b k_b kb
接下来看看上面的图,考虑点 K 3 K_3 K3,可以发现 K 3 K_3 K3 有一个糟糕的特点: s p o l e 2 , 3 &gt; s p o l e 3 , 4 spole_{2,3}&gt;spole_{3,4} spole2,3>spole3,4
这个特点意味着什么?假设 s p o l e 3 , 4 &gt; s u m [ i ] spole_{3,4}&gt;sum[i] spole3,4>sum[i],也就是说如果 k 3 k_3 k3 优于 k 4 k_4 k4,那么 s p o l e 2 , 3 &gt; s p o l e 3 , 4 &gt; s u m [ i ] spole_{2,3}&gt;spole_{3,4}&gt;sum[i] spole2,3>spole3,4>sum[i],即 k 2 k_2 k2 优于 k 3 k_3 k3
同理,如果 s p o l e 2 , 3 &lt; s u m [ i ] spole_{2,3}&lt;sum[i] spole2,3<sum[i],也就是说如果 k 2 k_2 k2 劣于 k 3 k_3 k3,则 s p o l e 3 , 4 &lt; s p o l e 2 , 3 &lt; s u m [ i ] spole_{3,4}&lt;spole_{2,3}&lt;sum[i] spole3,4<spole2,3<sum[i],因而 k 4 k_4 k4 优于 k 3 k_3 k3
因此,无论如何, k 3 k_3 k3 都不可能是本次转移的最优点。因此我们可以直接删除点 k 3 k_3 k3
AyqHbt.png
现在斜率是单调上升的,而且从某个值开始大于 s u m [ i ] sum[i] sum[i],考虑上面的结论,当 s p o l e a , b &gt; s u m [ i ] spole_{a,b}&gt;sum[i] spolea,b>sum[i] 时,靠右的点所表示的状态更优。
换句话说,我们最终转移的位置必然是上面所有 K K K 中,第一个满足 s p o l e i , i 的 下 一 个 点 &gt; s u m [ i ] spole_{i,i的下一个点}&gt;sum[i] spolei,i>sum[i] 的点,因此这里可以二分。
还有一个问题,如何加入新的点 K 6 K_6 K6
AyLaqI.png
对于上图,可以发现,加入点 K 6 K_6 K6 后, K 5 K_5 K5 变成了上升点,不可能成为最优解,因此可以直接删除,然后继续判断 K 4 K_4 K4 是否为上升点(是有可能的),以此类推。
AyL2ss.png
至此,转移的复杂度降到了 Θ ( log ⁡ n ) \Theta(\log n) Θ(logn),总时间复杂度为 Θ ( n k log ⁡ n ) \Theta(nk\log n) Θ(nklogn)
这样就够了吗?当然不。
可以发现,我们要求 K a K_a Ka 大于的东西 s u m [ i ] sum[i] sum[i],随着 i i i 的增加是在递增的,也就是说,如果这个 K a &lt; s u m [ i ] K_a&lt;sum[i] Ka<sum[i],那 K a K_a Ka 不可能再大于任何 s u m [ j ] ( j &gt; i ) sum[j](j&gt;i) sum[j](j>i),即 ∀ j &gt; i , K a &lt; s u m [ j ] \forall j&gt;i,K_a&lt;sum[j] j>i,Ka<sum[j],因此,我们大可直接删除这个 K a K_a Ka
这样,每个点最多进入这个序列 1 1 1 次,从这个序列出来 1 1 1 次,其实就是个(单调队列,故总时间复杂度为 Θ ( n k ) \Theta(nk) Θ(nk)

代码

#include <cstdio>
#include <cstring>
const int N=1e5+5,M=205;
int q[N],pre[N][M],n,k,cur;
long long sum[N],f[2][N];
double slope(int i,int j) {
    if(sum[i]==sum[j]) return -1;
    //由于原序列中有0的存在,因此需要特殊判断斜率为0的直线,否则会出现除以0错误
    return 1.0*((f[cur^1][i]-sum[i]*sum[i])-(f[cur^1][j]-sum[j]*sum[j]))/(sum[j]-sum[i]);
}
int main() {
    scanf("%d%d",&n,&k);
    for(int i=1,x; i<=n; ++i)
        scanf("%d",&x),sum[i]=sum[i-1]+x;
    for(int j=1,l,r; j<=k; ++j,cur^=1) {
        q[l=r=1]=0;
        for(int i=1; i<=n; ++i) {
            //去除不大于sum[i]的点 
            while(l<r&&slope(q[l],q[l+1])<=sum[i]) ++l;
            int &t=q[l];
            f[cur][i]=f[cur^1][t]+sum[t]*(sum[i]-sum[t]);
            pre[i][j]=t;
            //去除加入i可能导致的上升点 
            while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) --r;
            q[++r]=i;
        }
    }
    printf("%lld\n",f[cur^1][n]);
    for(int x=n,i=k; i>=1; --i)//输出路径
        x=pre[x][i],printf("%d%c",x," \n"[i==1]);
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值