2023牛客OI赛前集训营-提高组(第三场)C.分糖果

2023牛客OI赛前集训营-提高组(第三场)C.分糖果

文章目录

C-分糖果_2023牛客OI赛前集训营-提高组(第三场) (nowcoder.com)

题目大意

求前 i ( i ∈ 1 , n ) i(i\in1, n) i(i∈1,n) 个数分成 k k k 个连续的区间,每一个区间和的最大值最小是多少。

T T T 组数据

对于 30 p t s 30pts 30pts , 1 ≤ n ≤ 100 , 1 ≤ k ≤ n 1 \le n \le 100 , 1\le k \le n 1≤n≤100,1≤k≤n

另外 20 p t s 20pts 20pts , 1 ≤ n ≤ 1 0 4 , k = 1 1\le n \le 10^4 , k = 1 1≤n≤104,k=1

另外 50 p t s 50pts 50pts, 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ n 1\le n \le 10^5 , 1\le k \le n 1≤n≤105,1≤k≤n

对于全部数据有 T ≤ 3 , ∣ a i ∣ ≤ 1 0 9 , 1 ≤ n ≤ 1 0 5 T \le 3 , |a_i| \le 10^9 , 1\le n \le 10^5 T≤3,∣ai∣≤109,1≤n≤105

做法

考试忘记加换行, 50 p t s → 0 50pts \to 0 50pts→0

对于 30 p t s 30pts 30pts

直接二分答案 + d p dp dp , O ( n 2 ) O(n^2) O(n2) 判断能不能分成大于等于 k k k 块的和满足假设。

对于 20 p t s 20pts 20pts

直接前缀和乱搞

50 p t s 50pts 50pts 代码

cpp 复制代码
#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
#define LL long long
using namespace std;
const int N = 1e5 + 5 , M = 1e5 + 5;
const LL inf = 1e14 + 5;
int n , k;
LL a[M] , f[M] , s[M] , ans;
bool ck (LL x) {
    int l = 1;
    fu (i , 1 , n) f[i] = 0;
    fu (i , 1 , n) {
        if (i == 1) {
            f[i] = (s[1] <= x);
        }
        else {
            fu (j , 1 , i) {
                if (s[i] - s[j - 1] <= x && (f[j - 1] || j == 1)) f[i] = max (f[i] , f[j - 1] + 1);
            }
        }
        if (f[i] >= k) return 1;
    }
    return 0;
}
LL fans (LL l , LL r) {
    if (l == r) {
        return ck (l) ? l : inf;
    }
    LL mid = l + r >> 1;
    if (ck (mid)) return min (fans (l , mid) , mid);
    else return min (inf , fans (mid + 1 , r));
}
int main () {
    // freopen ("candy2.in" , "r" , stdin);
    int T; 
    scanf ("%d" , &T);
    while (T --) {
        scanf ("%d%d" , &n , &k);
        fu (i , 1 , n) scanf ("%lld" , &a[i]) , s[i] = 0;
        fu (i , 1 , n) s[i] = s[i - 1] + a[i];
        if (k == 1) {
            ans = inf;
            fu (i , 1 , n) ans = min (ans , s[i]);
            printf ("%lld\n" , ans);
            continue;
        }
        printf ("%lld\n" , fans (-inf , inf));
    }
}

对于 100 p t s 100pts 100pts

权值线段树 + 离散化

我们发现上面的 d p dp dp 转移太慢了

s s s 数组表示前缀和 ,当前二分答案为 x x x

对于 i ∈ 1 , n i\in1 , n i∈1,n 我们只用找到 j ∈ 1 , i j\in1 , i j∈1,i 满足 s i − s j ≤ x si - sj \le x si−sj≤x 即 s i − x ≤ s j si - x \le sj si−x≤sj 时的最大值 + 1 +1 +1


f i = M A X j = 1 i f j + 1 ( s i − x ≤ s j ) fi = MAX_{j = 1}^i fj + 1(si - x \le sj) fi=MAXj=1ifj+1(si−x≤sj)

用权值线段树维护就好了。

初始化 f 0 = 0 f0 = 0 f0=0

每次把 f i fi fi 插入权值线段树中

因为总和太大了,所以还要把 s i si si 和 s i − x si - x si−x 拿出来离散化(还有 0 0 0) ,动态开点。

cpp 复制代码
#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
#define LL long long
using namespace std;
const int N = 4e7 + 5 , M = 2e5 + 5;
const LL inf = 1e9 * 1e6;
const int inff = 1e9 + 5;
int n , k , cnt , mp[M];
LL a[M] , f[M] , s[M] , ans , ss[M << 1];
struct Tr {
    int v , lp , rp;
} tr[N];
void glp (int p) {
    if (!tr[p].lp) {
        tr[p].lp = ++cnt;
        tr[cnt].v = -inff;
    }
}
void grp (int p) {
    if (!tr[p].rp) {
        tr[p].rp = ++cnt;
        tr[cnt].v = -inff;
    }
}
void change (int p , LL l , LL r , int x , int val) {
    if (l == r) tr[p].v = max (tr[p].v , val);
    else {
        int mid = l + r >> 1;
        if (x <= mid) {
            glp (p);
            change (tr[p].lp , l , mid , x , val);
        }
        else {
            grp (p);
            change (tr[p].rp , mid + 1 , r , x , val);
        }
        tr[p].v = max (tr[tr[p].lp].v , tr[tr[p].rp].v);
    }
}
int query (int p , LL l , LL r , LL L , LL R) {
    if (L <= l && R >= r) return tr[p].v;
    else {
        int mid = l + r >> 1 , ans1 = -inff , ans2 = -inff;
        if (L <= mid && tr[p].lp) 
            ans1 = query (tr[p].lp , l , mid , L , R);
        if (R > mid && tr[p].rp) 
            ans2 = query (tr[p].rp , mid + 1 , r , L , R);
        return max (ans1 , ans2);
    }
}
void cl (int p) { tr[p].lp = tr[p].rp = 0 , tr[p].v = -inff; }
void clear (int p , LL l , LL r) {
    if (l == r) {
        cl (p);
        return;
    }
    int mid = l + r >> 1;
    if (tr[p].lp) 
        clear (tr[p].lp , l , mid);
    if (tr[p].rp)
        clear (tr[p].rp , mid + 1 , r);
    cl (p);
} 
bool ck (LL x) {
    int s1 = 1;
    ss[1] = 0;
    fu (i , 1 , n) {
        ss[++s1] = s[i];
        ss[++s1] = s[i] - x;
    }
    sort (ss + 1 , ss + s1 + 1);
    int m = unique (ss + 1 , ss + s1 + 1) - ss - 1;
    clear (1 , -M , M);
    tr[0].v = -M;
    cnt = 1;
    int y = lower_bound(ss + 1 , ss + s1 + 1 , 0) - ss;
    change (1 , -M , M , y , 0);
    fu (i , 1 , n) {
        y = lower_bound(ss + 1 , ss + s1 + 1 , s[i] - x) - ss;
        f[i] = query (1 , -M , M , y , M) + 1;
        y = lower_bound(ss + 1 , ss + s1 + 1 , s[i]) - ss;
        change (1 , -M , M , y , f[i]);
        if (f[i] >= k) return 1;
    }
    return 0;
}
LL fans (LL l , LL r) {
    if (l == r) return ck (l) ? l : inf;
    else {
        LL mid = l + r >> 1;
        if (ck (mid))  {
            return min (fans (l , mid) , mid);
        }
        else {
            return fans (mid + 1 , r);
        }
    }
}
int main () {
    // freopen ("candy2.in" , "r" , stdin);
    int T; 
    scanf ("%d" , &T);
    while (T --) {
        scanf ("%d%d" , &n , &k);
        fu (i , 1 , n) scanf ("%lld" , &a[i]) , s[i] = 0;
        fu (i , 1 , n) s[i] = s[i - 1] + a[i];
        printf ("%lld\n" , fans (-inf , inf));
    }
}
相关推荐
明月_清风39 分钟前
从二叉树到 B+ 树:一文搞懂工程中「树」的演化之道
数据结构·算法·go
渡我白衣43 分钟前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
hetao17338371 小时前
2026-09-01~09-04 hetao1733837 的刷题记录
c++·算法
Evand J1 小时前
【MATLAB例程,图像滤波5】 反谐波均值滑动窗口滤波(CHMF)图像降噪与质量评价,附代码下载链接
图像处理·算法·计算机视觉·matlab·均值算法·滑动窗口滤波·均值滑动
血小板要健康2 小时前
链表 阶段算法总结
java·数据结构·笔记·算法·leetcode·链表
a187927218312 小时前
【算法】回溯算法(三):三记重锤与 N 皇后——记忆化、状态设计与三层漏斗
算法·leetcode·go·剪枝·回溯·n皇后·算法讲解
HugoStudio_SWAN4 小时前
洛谷 P1420 / P1179 / B4262 最长连号、数字统计与词频统计——统计的三种面孔
c++·学习·程序人生·算法
青 春 记 忆4 小时前
LeetCode 350. 两个数组的交集 II|Python 解法详解
python·算法·leetcode
阳明山水5 小时前
销量预测2025—2026:从模型竞赛到系统融合的范式转型
人工智能·深度学习·算法·机器学习·架构
桐盛科技6 小时前
拒绝“漂绿”风险:基于边缘计算的碳排放数据清洗算法与实时校验逻辑
人工智能·算法·边缘计算