BZOJ3675 [APIO2014]序列分割(斜率优化模板)

58 篇文章 0 订阅

Address


Solution

  • 仿佛终于懂一点斜率优化了
  • f[i][r] f [ i ] [ r ] 表示第 r r 次分割到第 i 个位置的最大化得分, sum[i] s u m [ i ] 表示 j=1ia[j] ∑ j = 1 i a [ j ]
  • 分割的顺序和得分无关,转移显然为:
    f[i][r]=max{f[j][r1]+(sum[i]sum[j])×sum[j],0j<i} f [ i ] [ r ] = max { f [ j ] [ r − 1 ] + ( s u m [ i ] − s u m [ j ] ) × s u m [ j ] , 0 ≤ j < i }
  • 假设位置 k k 比位置 j(j>k) 相对于 i i 更优,则(先忽略第二维 r):

    f[k]+(sum[i]sum[k])×sum[k]f[k]+sum[i]×sum[k]sum[k]2(f[k]sum[k]2)(f[j]sum[j]2)(f[k]sum[k]2)(f[j]sum[j]2)sum[j]sum[k]>f[j]+(sum[i]sum[j])×sum[j]>f[j]+sum[i]×sum[j]sum[j]2>sum[i]×(sum[j]sum[k])>sum[i](21)(22)(23)(24) (21) f [ k ] + ( s u m [ i ] − s u m [ k ] ) × s u m [ k ] > f [ j ] + ( s u m [ i ] − s u m [ j ] ) × s u m [ j ] (22) f [ k ] + s u m [ i ] × s u m [ k ] − s u m [ k ] 2 > f [ j ] + s u m [ i ] × s u m [ j ] − s u m [ j ] 2 (23) ( f [ k ] − s u m [ k ] 2 ) − ( f [ j ] − s u m [ j ] 2 ) > s u m [ i ] × ( s u m [ j ] − s u m [ k ] ) (24) ( f [ k ] − s u m [ k ] 2 ) − ( f [ j ] − s u m [ j ] 2 ) s u m [ j ] − s u m [ k ] > s u m [ i ]

  • 我们记 G(k,j)=(f[k]sum[k]2)(f[j]sum[j]2)sum[j]sum[k] G ( k , j ) = ( f [ k ] − s u m [ k ] 2 ) − ( f [ j ] − s u m [ j ] 2 ) s u m [ j ] − s u m [ k ]

  • 考虑若存在一个选取位置的子序列 q1,q2,...,qm(1q1<q2<...<qmn) q 1 , q 2 , . . . , q m ( 1 ≤ q 1 < q 2 < . . . < q m ≤ n ) ,满足 sum[i]<G(q1,q2)<G(q2,q3)<...<G(qm1,qm) s u m [ i ] < G ( q 1 , q 2 ) < G ( q 2 , q 3 ) < . . . < G ( q m − 1 , q m )
  • q1 q 1 q2 q 2 优, q2 q 2 q3 q 3 优,…, qm1 q m − 1 qm q m 优,即 q1 q 1 是这个子序列中的最优解。
  • 这样,我们就可以用一个 G G 递增的单调队列来维护,保证每次从队首取出的都是最优解。
  • 考虑怎样维护这个单调队列。
  • 记单调队列 q 的队首为 head h e a d ,队尾为 tail t a i l
  • 因为 sum[i] s u m [ i ] 递增,若 G(q[head],q[head+1])<sum[i] G ( q [ h e a d ] , q [ h e a d + 1 ] ) < s u m [ i ] ,弹出 q[head] q [ h e a d ]
  • 进一步的,我们把 (sum[k],f[k]sum[k]2) ( − s u m [ k ] , f [ k ] − s u m [ k ] 2 ) (sum[j],f[j]sum[j]2) ( − s u m [ j ] , f [ j ] − s u m [ j ] 2 ) 看做平面直角坐标系上的两个点,则 G(j,k) G ( j , k ) 即为两点所在直线的斜率,这也是这种优化被叫做斜率优化的原因。
  • 那么 G G 递增转化成图形大概就长这样:
  • 这不就是下凸壳吗?
  • 既然是凸壳,自然要有凸壳的性质。
  • 回忆维护凸壳的过程,我们可以得到在插入位置 i 的维护过程:
    • G(q[tail1],q[tail])>G(q[tail],i) G ( q [ t a i l − 1 ] , q [ t a i l ] ) > G ( q [ t a i l ] , i ) ,则弹出 q[tail] q [ t a i l ] (可以自己画下图,如果不弹出它,凸壳就不凸了)
  • 时间复杂度 O(nK) O ( n K )

Code

//原题应该还要输方案,记录下最优转移的位置往回跳即可。
#include <iostream>
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long ll;

namespace inout
{
    const int S = 1 << 20;
    char frd[S], *ihed = frd + S;
    const char *ital = ihed;

    inline char inChar()
    {
        if (ihed == ital)
            fread(frd, 1, S, stdin), ihed = frd;
        return *ihed++;
    }

    inline int get()
    {
        char ch; int res = 0; bool flag = false;
        while (!isdigit(ch = inChar()) && ch != '-');
        (ch == '-' ? flag = true : res = ch ^ 48);
        while (isdigit(ch = inChar()))
            res = res * 10 + ch - 48;
        return flag ? -res : res;
    }

    char fwt[S], *ohed = fwt;
    const char *otal = ohed + S;

    inline void outChar(char ch)
    {
        if (ohed == otal)   
            fwrite(fwt, 1, S, stdout), ohed = fwt;
        *ohed++ = ch;
    }

    inline void put(int x)
    {
        if (x > 9) put(x / 10);
        outChar(x % 10 + 48);
    }   
};
using namespace inout;
const int N = 1e5 + 5, K = 205;
ll f[N][2]; int h[N], sum[N], g[N][K], n, k;
//洛谷卡空间,f数组滚动。

inline ll Sqr(ll x)
{
    return x * x;
}

inline double Slope(int j, int a, int b)
{
    if (sum[a] == sum[b]) return -1e18;
    return (double)(f[b][j] - Sqr(sum[b]) - f[a][j] + Sqr(sum[a]))
         / (double)(sum[a] - sum[b]); 
}

int main()
{

    n = get(); k = get();
    for (int i = 1; i <= n; ++i) 
        sum[i] = sum[i - 1] + get();
    for (int j = 1; j <= k; ++j)
    {
        int t = 0, w = 0,
            now = j & 1, lst = j - 1 & 1; 
        for (int i = 1; i <= n; ++i)
        {
            while (t < w && Slope(lst, h[t], h[t + 1]) <= sum[i]) ++t;
            f[i][now] = f[h[t]][lst] + (ll)(sum[i] - sum[h[t]]) * sum[h[t]];
            g[i][j] = h[t];
            while (t < w && Slope(lst, h[w - 1], h[w]) >= Slope(lst, h[w], i)) --w;
            h[++w] = i;
        }
    }

    cout << f[n][k & 1] << endl; 
    for (int i = k, x = n; i; --i)
        put(x = g[x][i]), outChar(' ');
    fwrite(fwt, 1, ohed - fwt, stdout);

    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值