[日常训练] seq 数列分割

Description

  • 给出一个长为 n n 的整数数列 a,要求求出最小的 m m ,使得存在一种将数列分成恰好 K 份的方案(每份是数列上连续的一段,且不得为空),每份的数字之和不大于 m m
  • 对于 100% 的数据满足: Kn15000,10000a[i]10000 K ≤ n ≤ 15000 , − 10000 ≤ a [ i ] ≤ 10000

Solution

  • 没有负数就是水题。
  • 首先想到二分答案 mid m i d ,考虑如何快速判断。
  • bool b o o l 数组 f[i][j] f [ i ] [ j ] 表示分割到序列第 i i 个位置、分成 j 个部分、满足每个部分和 mid ≤ m i d 是否可能,序列前缀和为 s[i] s [ i ] , 则转移为:
    f[i][j]|=f[k][j1](0k<i,s[i]s[k]mid)f[0][0]=true f [ i ] [ j ] | = f [ k ] [ j − 1 ] ( 0 ≤ k < i , s [ i ] − s [ k ] ≤ m i d ) , 初 始 时 f [ 0 ] [ 0 ] = t r u e
  • g[i]={f[i][0],f[i][1],...,f[i][K]} g [ i ] = { f [ i ] [ 0 ] , f [ i ] [ 1 ] , . . . , f [ i ] [ K ] } ,定义:
    • g[i]+g[j]={f[i][0] | f[j][0],f[i][1] | f[j][1],...,f[i][K] | f[j][K]} g [ i ] + g [ j ] = { f [ i ] [ 0 ]   |   f [ j ] [ 0 ] , f [ i ] [ 1 ]   |   f [ j ] [ 1 ] , . . . , f [ i ] [ K ]   |   f [ j ] [ K ] }
    • g[i]<<1={0,f[i][0],f[i][1],...,f[i][K1]} g [ i ] << 1 = { 0 , f [ i ] [ 0 ] , f [ i ] [ 1 ] , . . . , f [ i ] [ K − 1 ] }
  • 转移就变为 g[i]=(j=1i1g[j],s[j]s[i]mid)<<1 g [ i ] = ( ∑ j = 1 i − 1 g [ j ] , s [ j ] ≥ s [ i ] − m i d ) << 1
  • s[i] s [ i ] 按从大到小排序,可知此时合法的 j j 是一个前缀。
  • 因此把 s[i] 离散化,作为树状数组的下标维护前缀和来转移。
  • 此时 DP D P 复杂度 O(nKlogn) O ( n K log ⁡ n ) ,仍然不能通过。
  • 进一步地,我们会发现: g[i] g [ i ] 中为 true t r u e 的部分一定是一段连续的区间,只需要用区间的左右边界来表示。
  • 容易证明,一开始 g[0]={1,0,...,0} g [ 0 ] = { 1 , 0 , . . . , 0 } 满足条件,之后的转移只涉及上述两种运算,因此也满足条件。
  • 此时就可设 g[i]={l[i],r[i]} g [ i ] = { l [ i ] , r [ i ] } ,初始时 g[0]={0,0} g [ 0 ] = { 0 , 0 } ,则:
    • g[i]+g[j]={min{l[i], l[j]}, max{r[i], r[j]}} g [ i ] + g [ j ] = { min { l [ i ] ,   l [ j ] } ,   max { r [ i ] ,   r [ j ] } }
    • g[i]<<1={l[i]+1, min{r[i]+1, K}} g [ i ] << 1 = { l [ i ] + 1 ,   min { r [ i ] + 1 ,   K } }
  • 因此计算 g[i] g [ i ] 的部分就被优化掉了, DP D P 的时间复杂度 O(nlogn) O ( n log ⁡ n )

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>

using namespace std;

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; 
    }
};
using namespace inout;

const int M = 15005;
const int Maxn = 0x3f3f3f3f;
int n, K, m, l = Maxn, r = -Maxn, Ans;
int f[M], g[M], sum[M], to[M], b[M];

inline int Max(int x, int y) {return x > y ? x : y;}
inline int Min(int x, int y) {return x < y ? x : y;} 
inline void CkMin(int &x, int y) {if (x > y) x = y;}
inline void CkMax(int &x, int y) {if (x < y) x = y;} 

struct line
{
    int l, r;

    line() {}
    line(int L, int R):
        l(L), r(R) {}

    inline void operator += (const line &x) 
    {
        CkMin(l, x.l); CkMax(r, x.r);
    }
}c[M], h[M];

const line Inf = line(Maxn, -Maxn);
inline line Right(const line &x)
{
    if (x.l == K) return Inf;
    return line(x.l + 1, Min(x.r + 1, K));
}

inline void Modify(int x, const line &y)
{
    for (; x <= n; x += x & -x)
        c[x] += y;
}

inline line Query(int x)
{
    line res = Inf;
    for (; x; x -= x & -x)
        res += c[x];
    return res; 
}

inline bool check(int mid)
{
    for (int i = 1; i <= n; ++i) c[i] = Inf;
    Modify(to[0], h[0] = line(0, 0)); 
    for (int i = 1; i < n; ++i)
    {
        int k = upper_bound(b + 1, b + m + 1, mid - sum[i]) - b - 1;
        h[i] = Right(Query(k)); Modify(to[i], h[i]);
    }
    return h[n - 1].r == K;
}

int main()
{   
    n = get(); K = get();
    for (int i = 1; i <= n; ++i) 
    {
        f[i] = g[i] = sum[i] = get();
        sum[i] += sum[i - 1]; b[i] = -sum[i];
        if (f[i - 1] > 0) f[i] += f[i - 1];
        if (g[i - 1] < 0) g[i] += g[i - 1];
        CkMin(l, g[i]); CkMax(r, f[i]);
    }
    sort(b + 1, b + n + 2);
    m = unique(b + 1, b + n + 2) - b - 1;
    for (int i = 0; i <= n; ++i)
        to[i] = lower_bound(b + 1, b + m + 1, -sum[i]) - b; 

    ++n;
    while (l <= r)
    {
        int mid = (l + r) / 2;
        if (check(mid)) Ans = mid, r = mid - 1;
            else l = mid + 1;
    }
    printf("%d\n", Ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值