IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 2022牛客寒假1 中位数切分(推结论 or 线段树优化dp + 前缀和) -> 正文阅读

[数据结构与算法]2022牛客寒假1 中位数切分(推结论 or 线段树优化dp + 前缀和)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题意:

给定一个 长为 n 的数组 a一个整数 m,你需要将其 切成连续的若干段,使得 每一段的中位数大于等于 m,求 最多可以划分成多少段

思路:

这是一道十分有趣的题目,预期的简单解法 O(n)复杂解法 O(nlogn)

一、简单解法:

数列中 ≥ m 的数字cnt1 个,< m 的数字cnt2,则 答案为 cnt1 - cnt2该值 ≤ 0 时输出 -1

证明一(严谨):

f(l, r)原数组中 a[l],...,a[r] 一段中的元素 对应的 cnt1 - cnt2 的值

f() 的性质:

  • f(l, r) > 0 表示该段单独拿出来满足中位数 ≥ m

  • f(l, r) = f(l, mid) + f(mid + 1, r)

原问题 f(1, n) ≤ 0 时 输出 -1 是显然的。

欲证明

f(1, n) > 0f(1, n) 即为原问题答案

考虑 初始 所有数组成了一个大区间,我们在通过 切大区间若干段 来得到 最终答案

若可以 找到一个位置 mid使 f(1, mid) > 0 && f(mid + 1, n) > 0,则 沿 mid 将数组切开 得到的 两部分中位数 依然 满足条件,此时 区间数 += 1

所以我们要探究 什么时候数组可以切

定理

当且仅当 f(l, r) > 1 存在 一种切法 mid 使 f(l, mid) > 0 && f(mid + 1, r) > 0

证明

有一个位置 mid 使得 f(l, mid) = 1,则 该位置满足条件的切割位置,因为 此时 f(l, mid) = 1 > 0, f(mid + 1, r) = f(l, r) - f (l, mid) > 1 - 1 = 0

又因为 f(l, l - 1) = 0(表示 空区间)且 f(l, r) > 1f(l, x) -> f(l, x + 1)值只会变化 1,因此过程中 一定存在某一时刻 mid 使得 f(l, mid) = 1

由上,只要 f(l, r) > 1 就可以切,而我们又希望 切得尽可能多,因此 最终状态 一定是 所有切割得到的段都有 f(l, r) = 1 (否则 还可以再切)。

因此
在这里插入图片描述

又因为 初始时
在这里插入图片描述
所以 最终切成的段数 = cnt1 - cnt2,证毕。

证明二(不严谨):

先假设 数组里只有 ≥ m 的数字们(想象有一个 只含 ≥ m 的数长为 cnt2 的数组),他们都自己作为一个区间,则 一共有 cnt1 个区间,且这些区间都是 满足中位数 ≥ m 的要求 的。

然后考虑 插一个 < m 的数字进去,这个数字 需要和 两个 > m 的数组 组成一个区间 才可以 满足中位数要求,因此 原来两个区间变成了一个区间区间数减一,然后 删掉这个数某个 > m的数数组长变为 cnt2 - 1

(你可能会说这 cnt2 - 1 里有一个数其实是在刚才新组成的区间里,这没问题吗,但 这种新组成的区间并不影响上述操作,所以 可以看作一个 > m 的数字

以此类推,可以看出 每个 < m 数字的作用就是说区间数减一

二、复杂解法:(借鉴一位大佬博主)

使用 线段树 + 前缀和 + DP,将 O(n ^ 2) 的 DP 优化到 O(nlogn)

划分满足 无后效性,想到 dp,则设:dp[i] 表示 考虑前 i 个元素最多划分段数

看到 中位数,想到把元素变化。ai ≥ m ,即为 1,否则记为 -1

这样,序列的 中位数 ≥ m 转化为 序列的和 ≥ 1

dp 的转移,如下:
在这里插入图片描述
元素和,想到 前缀和。我们用 pre 数组记录前缀和(代码中用 s 数组表示),于是后面就变成了:
在这里插入图片描述
由于 pre[i] 是固定的,我们 移动元素位置,得到:
在这里插入图片描述
于是,右边就变成了 求区间最大值线段树 搞定。

最后 答案当然就是 dp[n]

注意到,pre 数组是可以为负数的,所以我们 每个位置的元素增加一个增量 M = 1e5 + 5 即可

还注意到,有些位置的分隔是不可行的,因为求的是 区间最大值-1 做初始化

代码:(做法一)

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e5+10;
ll num[N];

int main()
{
    int t; scanf("%d", &t);
    while(t--)
    {
        int n, m; scanf("%d%d", &n, &m);
        int a = 0, b = 0;
        for(int i=0; i<n; ++i)
        {
            scanf("%lld", &num[i]);
            if(num[i]>=m) ++a;
            else ++b;
        }
        int ans = a - b;
        if(ans > 0) printf("%d\n", ans);
        else puts("-1");
    }
    
    return 0;
}

代码:(更新做法二 线段树优化dp + 前缀和)

#include<bits/stdc++.h>

using namespace std;

const int N = 2e5+10;
const int M = N>>1;
struct node
{
    int l, r;
    int v;
} t[N<<2];
int s[N];

void pushup(int u)
{
    t[u].v = max(t[u<<1].v, t[u<<1|1].v);
}

void build(int u, int l, int r)
{
    t[u] = {l, r};
    if(l==r) {t[u].v = -1; return ;}
    int mid = l + r >> 1;
    build(u<<1, l, mid), build(u<<1|1, mid+1, r);
    pushup(u);
}

void modify(int u, int x, int v)
{
    if(t[u].l==x&&t[u].r==x) {t[u].v = v; return ;}
    int mid = t[u].l + t[u].r >> 1;
    if(x<=mid) modify(u<<1, x, v);
    if(x>mid) modify(u<<1|1, x, v);
    pushup(u);
}

int ask(int u, int l, int r)
{
    if(l<=t[u].l&&r>=t[u].r) return t[u].v;
    int res = -1;
    int mid = t[u].l + t[u].r >> 1;
    if(l<=mid) res = max(res, ask(u<<1, l, r));
    if(r>mid) res = max(res, ask(u<<1|1, l, r));
    return res;
}

int main()
{
    int t; scanf("%d", &t);
    while(t--)
    {
        int n,m; scanf("%d%d",&n,&m);
        for(int i=1; i<=n; ++i)
        {
            int t; scanf("%d",&t);
            s[i] = s[i-1] + (t>=m ? 1 : -1);
        }
        build(1, 1, N);
        modify(1, 0 + M, 0);
        
        int ans = -1;
        for(int i=1; i<=n; ++i)
        {
            int dpj = ask(1, 1, s[i] - 1 + M);
            if(dpj!=-1)
            {
                int dpi = dpj + 1;
                if(i==n) ans = dpi;
                modify(1, s[i] + M, dpi);
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-01 15:57:51  更:2022-05-01 15:58:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:55:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码