当前位置: 首页 > news >正文

洛谷普及B3691 [语言月赛202212] 狠狠地切割(Easy Version)

题目:[语言月赛202212] 狠狠地切割(Easy Version)

题号:B3691 

难度:普及一

末尾包含对二分法优化的详细解释


题目分析

最后一句应该是本题的考查关键,关于筛选算法的时间优化,

但从功能理论上,我找到了两种方法,但是由于时间的限制,

一种是标记法,第二种是替换法,最终还是要加入二分法来优化时间。

果然,时间永远无法忽视,“时间是世界上一切成就的土壤。时间给空想者痛苦,给创造者幸福”

所以,为了幸福着想,写程序记得优化时间喔。

引入思路和代码

关于切割,我有以下思路,如果切割的位置不是某段的端点,那么本次切割就会产生一个额外的一段,否则不然,所以切割的位置决定了本次切割是 “ 有效切割 ” 还是 “ 无效切割 ” 。

不妨设定一个结构体,结构体元素如下

struct stu{
long long num;
int k;
};

scanf("%lld %lld",&n,&m);
struct stu sn[n+2];
struct stu sm[m+2];

其中num就和正常的数组一样,存取当前的数值,然后k用于标记作用,

因为每次在当前位置切割,如果有效,那么该位置的前一个位置和后一个位置定会分别变成两个数列的端点,此时这三个位置就会变成无法产生有效切割的切割点。

例如:原数列 ( 1 2 3 4 5 6 7 8 ) ,其中无法产生有效切割的无效切割点只有两个端点1和8,

如果在3处切割,将会变成 (1 2) 3 (4 5 6 7 8) 两个段。然后就变成1 2 4 8 四个无效切割点。

而我们要做的就是把变成无效切割点的元素标记起来,这样下次对某位置切割就能判断是否有效切割。我们需要一下代码。

for(int u=1;u<=m;u++){
scanf("%lld",&sm[u].num);
sm[u].k=0;}
sn[1].k=1;
sn[n].k=1;
for(int y=1;y<=m;y++)
for(int x=1;x<=n;x++)
if(sm[y].num==sn[x].num&&sn[x].k!=1)
{
pn++;
sn[x-1].k=1;
sn[x].k=1;
sn[x+1].k=1;
}

该代码,首先两端是默认被标记上的无效切割点,然后每次切割就会将当前被切割的点,和相邻两边的点变成无效切割点(因为已经是新的段落的端点),这样的话,不是有效切割的切割操作我们完全可以忽略过去,只需要统计被有效切割的点即可。

该方案的源代码是

#include<stdio.h>

struct stu{
long long num;
int k;
};

int main()
{
long long n,m,pn=1;
scanf("%lld %lld",&n,&m);
struct stu sn[n+2];
struct stu sm[m+2];
for(int i=1;i<=n;i++){
scanf("%lld",&sn[i].num);
sn[i].k=0;}
for(int u=1;u<=m;u++){
scanf("%lld",&sm[u].num);
sm[u].k=0;}
sn[1].k=1;
sn[n].k=1;
for(int y=1;y<=m;y++)
for(int x=1;x<=n;x++)
if(sm[y].num==sn[x].num&&sn[x].k!=1)
{
pn++;
sn[x-1].k=1;
sn[x].k=1;
sn[x+1].k=1;
}
printf("%lld",pn);
return 0;}

该方案在时间复杂度上肯定满足不了该题,但是思路是个好思路,得到的答案也是正确的,起重工pn++,是因为每次有效的切割都会多产生一个新的段落,而原本就有一个主段落,所以pn=1;

第二种优化方案

看完第一个标记法,可以看出,被标记上的数已经没用了,遍历到相应的值也会跳过掉,那既然没用了,干脆不标记了,直接替换掉就行了。

我们来看一种新的方法,替换法。

我们每次切割掉的数,直接将其换成一个不会影响后续操作,而且又能分辨出的数值,比如我取的-1,例如原本的数列  1  2  3  4  5  6  7  8  9,如果切割其中的3 和7,8.

得到的新数列就是  1  2  -1  4  5  6  -1  -1  9;从中我们可以看出,就算切割的是无效端点,那么也不会影响到什么,因为-1叠加起来也不会影响到段落的分布。

最后我们只需要检测其中段落的个数,检测的方法也很简单,我们首先在数组的末端再插一个-1.

这样我们只需要检测a[i]!=-1&&a[i+1]==-1;满足这个条件说明检测到了一个小段的末尾。

所以代码如下。(同样由于时间问题,在我的方法的基础上,使用二分法进行优化,替代掉最后一步双层遍历的操作)

#include<stdio.h>
#include<stdlib.h> 


int compare(const void *a, const void *b) {
    return *(long long*)a - *(long long*)b;
}

int main()
{
long long n,m;
scanf("%lld %lld",&n,&m);
long long a[n+2];
long long b[m+2];  
    
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(int i=1;i<=m;i++)
        scanf("%lld",&b[i]);
    
    a[n+1]=-1;
    
  
    qsort(b+1, m, sizeof(long long), compare);  
    

    for(int x=1;x<=n;x++) {
        long long *res = bsearch(&a[x], b+1, m, sizeof(long long), compare);
        if(res) a[x] = -1;
    }
    
    long long p=0;
    for(int i=1;i<=n;i++)
        if(a[i]!=-1 && a[i+1]==-1)
            p++;
    printf("%lld",p);
    return 0;
}

关于二分法,时间复杂度从本来的双层遍历O(n*m)变成O(n*logm),

其实二分法也是特别简单,不过是对半一致缩小范围,在这里多一条来解释一下二分法 

二分法查找(Binary Search),也被叫做折半查找,是一种高效的查找算法,不过它要求被查找的数组必须是有序的(一般是升序)。该算法的核心思想是通过不断将查找区间一分为二,逐步缩小查找范围,直至找到目标元素或者确定目标元素不存在。下面为你详细介绍其原理、代码实现以及复杂度分析。

原理

  1. 初始化:设定两个指针,left 指向数组的起始位置,right 指向数组的末尾位置。
  2. 查找过程
    • 计算中间位置 mid = (left + right) / 2
    • 把中间位置的元素 arr[mid] 与目标元素 target 进行比较:
      • 若 arr[mid] 等于 target,则查找成功,返回 mid
      • 若 arr[mid] 大于 target,表明目标元素在左半部分,更新 right = mid - 1
      • 若 arr[mid] 小于 target,表明目标元素在右半部分,更新 left = mid + 1
  3. 终止条件
    • 若找到目标元素,返回其索引。
    • 若 left 大于 right,意味着查找区间为空,目标元素不存在,返回 -1。

代码举例

#include <stdio.h>

// 二分查找函数
int binarySearch(int arr[], int n, int target) {
    int left = 0;
    int right = n - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target) {
            return mid;  // 找到目标元素,返回其索引
        } else if (arr[mid] < target) {
            left = mid + 1;  // 目标元素在右半部分
        } else {
            right = mid - 1;  // 目标元素在左半部分
        }
    }

    return -1;  // 未找到目标元素
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 11, 13};
    int n = sizeof(arr) / sizeof(arr[0]);
    int target = 7;

    int result = binarySearch(arr, n, target);

    if (result != -1) {
        printf("目标元素 %d 的索引是 %d\n", target, result);
    } else {
        printf("未找到目标元素 %d\n", target);
    }

    return 0;
}

代码解释

  1. binarySearch 函数

    • 该函数接收一个有序数组 arr、数组长度 n 以及目标元素 target 作为参数。
    • 运用 while 循环持续缩小查找区间,直至找到目标元素或者查找区间为空。
    • 计算中间位置 mid 时,采用 left + (right - left) / 2 来避免整数溢出。
    • 若找到目标元素,返回其索引;若未找到,返回 -1。
  2. main 函数

    • 定义一个有序数组 arr,并计算其长度 n
    • 设定目标元素 target 为 7。
    • 调用 binarySearch 函数进行查找,并依据返回结果输出相应信息。

复杂度分析

  • 时间复杂度:每次查找都会将查找区间缩小一半,因此时间复杂度为 O(logn),这里的 n 是数组的长度。
  • 空间复杂度:仅使用了常数级的额外空间,所以空间复杂度为 O(1)。

综上所述,二分法查找是一种高效的查找算法,不过前提是数组必须有序。在处理大规模有序数据时,它能显著提升查找效率。

http://www.dtcms.com/a/122208.html

相关文章:

  • 优化 Web 性能:移除未使用的 CSS 规则(Unused CSS Rules)
  • The packaging for this project did not assign a file to the build artifact
  • 02.使用cline(VSCode插件)、continue(IDEA插件)、cherry-studio玩转MCP
  • Android里面开子线程的方法
  • OpenHarmony子系统开发 - 调测工具(二)
  • 柑橘病虫害图像分类数据集OrangeFruitDataset-8600
  • Python: 实现数据可视化分析系统
  • Coze平台 发布AI测试Agent的完整实现方案
  • redis_exporter服务安装并启动
  • STL-list链表
  • mac 苍穹外卖 后端初始 SkyApplication 报错
  • HTTP:一.概述
  • 【Leetcode-Hot100】移动零
  • 净室软件工程:以数学为基石的高可靠性软件开发之道
  • 数学建模--在新能源汽车研发测试中的革命性应用
  • 最小覆盖子串 -- 滑动窗口
  • MMO 架构梳理
  • 分布式ID生成器设计详解
  • 直流有刷电机与H桥驱动
  • JavaScript数据结构-Map的使用
  • Oracle JDBC驱动 ojdbc14:使用指南与版本说明(附资源下载)
  • 建筑兔零基础自学记录69|爬虫Requests-2
  • 开篇 - Unlua+VsCode的智能提示、调试
  • 缓存工具类
  • 嵌入式 C语言 位操作 宏命令
  • spring boot 2.7 集成 Swagger 3.0 API文档工具
  • centos7系统搭建nagios监控
  • 【AI】MCP+cline 实现github官网项目查询
  • .vue文件中组件名称的设置,<script>标签的 name 属性说明
  • JS—浏览器的缓存策略:3分钟精通浏览器缓存策略