[POJ 3709] K-Anonymous Sequence(斜率优化dp / 动态维护凸包)

K-Anonymous Sequence

看了两年前自己的博客,真的好青涩,连 markdown 都不太会用。

确实观赏感不是很好。

学习真的是慢慢积累的过程,以前感觉理解很困难的事,随着知识的增长,现在都基本悟了。

problem

POJ3709

solution

f ( i ) : f(i): f(i): 考虑到前 i i i 个数,并在 i i i 个数划分的最小花费。

可以直接列出状态转移,枚举前一段的位置 j j j ,则 [ j + 1 , i ] [j+1,i] [j+1,i] 要求相同,且长度要够长。

f ( i ) = min ⁡ { f ( j ) + s u m ( i ) − s u m ( j ) + a [ j + 1 ] ∗ ( i − j ) } ( j ≤ i − m ) f(i)=\min\Big\{f(j)+sum(i)-sum(j)+a[j+1]*(i-j)\Big\}(j\le i-m) f(i)=min{f(j)+sum(i)sum(j)+a[j+1](ij)}(jim)

其中 s u m ( i ) : sum(i): sum(i): i i i 个数的和,即前缀和。

注意到题目给的性质 a [ i ] a[i] a[i] 是单增的。

考虑两个合法转移点 j < k j<k j<k ,且 j j j 不优于 k k k

即满足: f ( j ) + s u m ( i ) − s u m ( j ) + a [ j + 1 ] ∗ ( i − j ) ≥ f ( k ) + s u m ( i ) − s u m ( k ) + a [ k + 1 ] ∗ ( i − k ) f(j)+sum(i)-sum(j)+a[j+1]*(i-j)\ge f(k)+sum(i)-sum(k)+a[k+1]*(i-k) f(j)+sum(i)sum(j)+a[j+1](ij)f(k)+sum(i)sum(k)+a[k+1](ik)

化简得: f ( j ) − s u m ( j ) − a [ j + 1 ] ∗ j − f ( k ) + s u m ( k ) + a [ k + 1 ] ∗ k ≥ i ∗ ( a [ j + 1 ] − a [ k + 1 ] ) f(j)-sum(j)-a[j+1]*j-f(k)+sum(k)+a[k+1]*k\ge i*\big(a[j+1]-a[k+1]\big) f(j)sum(j)a[j+1]jf(k)+sum(k)+a[k+1]ki(a[j+1]a[k+1])

一般来说,斜率优化是把 i i i 的系数简化为 1 1 1 的分式形式。但是有些题目可能存在“平台”,即系数算出来为 0 0 0

除法分母不能是 0 0 0,所以为了防止程序死掉,乘法形式才是最好选择。

但是因为不等式的存在必须要求 a a a 的单调性,否则中途的决策点选择有不等式的变号,就不是斜率优化了。

李超树才是我们的红太阳!

单调队列存储合法转移点,每次取最优的队首转移。

对于队首的维护是非常简单的。

但斜率优化难以理解的问题就在队尾的维护,即常说的凸包维护。

网上很多就直接上凸包图像,然后从直线斜率角度入手,这里不赘述。

以此题为例,用语言化的最优点选择来尝试阐释斜率问题。

一般会有,令 Y ( j , k ) = f ( j ) − s u m ( j ) − a [ j + 1 ] ∗ j − f ( k ) + s u m ( k ) + a [ k + 1 ] ∗ k X ( j , k ) = a [ j + 1 ] − a [ k + 1 ] Y(j,k)=f(j)-sum(j)-a[j+1]*j-f(k)+sum(k)+a[k+1]*k\quad X(j,k)=a[j+1]-a[k+1] Y(j,k)=f(j)sum(j)a[j+1]jf(k)+sum(k)+a[k+1]kX(j,k)=a[j+1]a[k+1]

如果只考虑两个选择点 ( x , y ) (x,y) (x,y),显然当 i i i 增大后,不等式可能存在反向的问题,即 x x x 不一定就永远差于 y y y。【现在是要考虑 i i i 后面点的转移, i i i 点也是转移点】

所以是考虑三个点 ( x , y , z ) , x < y < z (x,y,z),x<y<z (x,y,z),x<y<z【这对应着计算 i i i 后, i i i 与凸包上的最后一个点形成的直线,以及凸包最后两个点形成的直线,两个直线的维护】

因为本题是最小值,所以当 Y ( x , y ) X ( x , y ) ≥ Y ( y , z ) X ( y , z ) ⇒ Y ( x , y ) ⋅ X ( y , z ) ≥ Y ( y , z ) ⋅ X ( x , y ) \frac{Y(x,y)}{X(x,y)}\ge \frac{Y(y,z)}{X(y,z)}\Rightarrow Y(x,y)·X(y,z)\ge Y(y,z)·X(x,y) X(x,y)Y(x,y)X(y,z)Y(y,z)Y(x,y)X(y,z)Y(y,z)X(x,y) 时,去掉 y y y。【维护凸包】

接下来解释为什么??

回到上面式子去,因为 a a a 是单增,所以 j < k ⇒ X ( j , k ) < 0 j<k\Rightarrow X(j,k)<0 j<kX(j,k)<0 。即 Y ( j , k ) X ( j , k ) ≤ i \frac{Y(j,k)}{X(j,k)}\le i X(j,k)Y(j,k)i,证明 j j j 不优于 k k k,且注意到 Y ( j , k ) X ( j , k ) \frac{Y(j,k)}{X(j,k)} X(j,k)Y(j,k) 是一个常数,与枚举点 i i i 无关。

现在回到这里,对于某个 i i i,如果 Y ( y , z ) X ( y , z ) ≥ i \frac{Y(y,z)}{X(y,z)}\ge i X(y,z)Y(y,z)i 那么一定有 Y ( x , y ) X ( x , y ) ≥ i \frac{Y(x,y)}{X(x,y)}\ge i X(x,y)Y(x,y)i

即,如果 y y y 优于 z z z,那么 x x x 一定也优于 y y y,那就没有 y y y 什么事了。【对应的上凸包维护,即斜率逐渐递减】

code

#include <cstdio>
using namespace std;
#define int long long
#define maxn 500005
int T, n, m;
int a[maxn], q[maxn], f[maxn], sum[maxn];

int Y( int j, int k ) {
    return f[j] - sum[j] + j * a[j + 1] - f[k] + sum[k] - k * a[k + 1];
}

int X( int j, int k ) {
    return a[j + 1] - a[k + 1];
}

signed main() {
    scanf( "%lld", &T );
    while( T -- ) {
        scanf( "%lld %lld", &n, &m );
        for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] ), sum[i] = sum[i - 1] + a[i];
        int head = 1, tail = 0; q[++ tail] = 0;
        for( int i = 1;i <= n;i ++ ) {
            while( head < tail and Y( q[head], q[head + 1] ) >= i * X( q[head], q[head + 1] ) ) head ++;
            int j = q[head];
            f[i] = f[j] + sum[i] - sum[j] - a[j + 1] * ( i - j );
            int k = i - m + 1;
            if( k >= m ) {
                while( head < tail and Y( q[tail - 1], q[tail] ) * X( q[tail], k ) >= Y( q[tail], k ) * X( q[tail - 1], q[tail] ) )
                    tail --;
                q[++ tail] = k;
            }
        }
        printf( "%lld\n", f[n] );
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值