HDU 3507 Print Article 题解(斜率优化)

题意简述

n ( 0 ≤ n ≤ 5 × 1 0 5 ) n(0 \leq n \leq 5 \times 10^5) n(0n5×105)个单词与一常数 m ( 0 ≤ m ≤ 1 0 3 ) m(0 \leq m \leq 10 ^ 3) m(0m103),第 i i i个单词有一属性 C i C_i Ci(原题未给出 C i C_i Ci的取值范围,但应为正整数或非负整数),需要将所有这些单词按顺序分为任意组,若将第 a a a b b b个单词分为一组,则花费为 ( ∑ i = a b C i ) 2 + m (\sum^b_{i = a}C_i)^2 + m (i=abCi)2+m,求出最少花费。有多组数据,以EOF结束输入。

思路

斜率优化板子题。设 f i f_i fi为将前 i i i个单词分组后的最小花费, s i s_i si ∑ k = 1 i C k \sum^i_{k = 1}C_k k=1iCk(即 C C C的前缀和),可写出状态转移方程为 f i = min ⁡ ( f j + ( s i − s j ) 2 + m ) ( 0 ≤ j < i ) f_i = \min(f_j + (s_i - s_j)^2 + m)(0 \leq j < i) fi=min(fj+(sisj)2+m)(0j<i) f n f_n fn即为最优解。若对于每一个 i i i,暴力枚举 j j j,时间复杂度是 O ( n 2 ) O(n^2) O(n2),显然无法通过本题。

优化

将方程移项,得 f i − s i 2 − m = min ⁡ ( f j + s j 2 − 2 s i s j ) ( 0 ≤ j < i ) f_i - s_i^2 - m = \min(f_j + s_j^2 - 2s_is_j)(0 \leq j < i) fisi2m=min(fj+sj22sisj)(0j<i)。作如下代换:

  • y j = f j + s j 2 y_j = f_j + s_j^2 yj=fj+sj2
  • k i = 2 s i k_i = 2s_i ki=2si
  • x j = s j x_j = s_j xj=sj
  • b i = f i − s i 2 − m b_i = f_i - s_i^2 - m bi=fisi2m

b i = min ⁡ ( y j − k i x j ) b_i = \min(y_j - k_ix_j) bi=min(yjkixj),恰好化为了直线方程的斜截式形式,且对于每个 x j , y j x_j, y_j xj,yj,都能够将其转换为平面直角坐标系上的一点 ( x j , y j ) (x_j, y_j) (xj,yj)。于是,此时的任务变为已知直线的斜率 k i k_i ki,需寻找一点 ( x j , y j ) (x_j, y_j) (xj,yj)使得该直线过该点的截距 b i b_i bi最小。

注意到 C i ≥ 0 C_i \geq 0 Ci0,可知 s i s_i si单调递增,则 k i k_i ki同样单调递增。我们可以将斜率为 k i k_i ki的直线从下往上移,该直线碰到的第一个点 ( x j , y j ) (x_j, y_j) (xj,yj)即为使得截距 b i b_i bi最小的点,此时 f i = f j + ( s i − s j ) 2 + m f_i = f_j + (s_i - s_j)^2 + m fi=fj+(sisj)2+m,即为此时的最优解。如图:

图1

此时该直线由下往上运动碰到的第一个点即为所求。易知该直线碰到的第一个点一定是这些点所组成的下凸壳的顶点,如图:

在这里插入图片描述

所以我们只需要维护之前所有的点所组成的下凸壳,每次判断直线与凸壳相交的顶点即可,可以使用单调队列维护每个点的编号。由于 k i k_i ki单调递增,所以这次碰到的点一定是上次碰到的点或者其右边的点。判断碰到的点可以通过比较一个顶点相邻两条边的斜率和直线的斜率,设 s l o p e ( i , j ) \mathop{\mathrm{slope}}(i, j) slope(i,j)为编号为 i , j i, j i,j的两点的斜率(非横坐标为 i , j i, j i,j!), q i q_i qi为队列中第i个元素,若 s l o p e ( q j − 1 , q j ) ≤ k i < s l o p e ( q j , q j + 1 ) \mathop{\mathrm{slope}}(q_{j - 1}, q_j) \leq k_i < \mathop{\mathrm{slope}}(q_j, q_{j + 1}) slope(qj1,qj)ki<slope(qj,qj+1),则 q i q_i qi即为所求点的编号。由于下次的点编号一定大于等于这次的点编号,所以可以定义一个变量表示这次在队列中的下标,下次直接判断递增即可。每次求完 f i f_i fi之后需要更新此时的凸壳。总时间复杂度 O ( n ) O(n) O(n)
代码:

#include <cstdio>
#define y(g) (f[g] + sqr(x(g)))
#define x(g) (sum[g])
#define sqr(x) ((x) * (x))

using namespace std;
typedef long long ll;

int n, m, q[500005], s, e;
ll sum[500005], f[500005];

int main() {
    while (~scanf("%d %d", &n, &m)) {
        s = e = 1; //此处隐含q[1] = 0,表示第一个编号是0,对应将前i个单词全部分为一组的情况
        
        for (int i = 1; i <= n; ++i) {
            scanf("%lld", &sum[i]);
            sum[i] += sum[i - 1];
            
            while (s < e && y(q[s + 1]) - y(q[s]) <= 2 * x(i) * (x(q[s + 1]) - x(q[s]))) ++s;
            
            f[i] = f[q[s]] + sqr(x(i) - x(q[s])) + m;
            
            while (s < e && (y(q[e]) - y(q[e - 1])) * (x(i) - x(q[e])) >= (y(i) - y(q[e])) * (x(q[e]) - x(q[e - 1])))
                --e;
            
            q[++e] = i;
        }
        
        printf("%lld\n", f[n]);
    }
    
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值