POJ 2104 K-th Number 平方分割 / 线段树

news2024/11/19 17:26:21

一、题目大意

长度为n(n<=100000)的数组,进行m次查询(m<=5000),每次查询时,输入为 i j k,返回为数组 [i,j] 的分片里第k大数字(1<=i<=j<=n,k<=j-i+1)

二、解题思路

1、平方分割

如果采用朴素的方法去计算,针对每次的i 和 j,从 i 到 j 循环把数组的元素放在一个tmp数组里,然后给tmp数组排序,输出tmp[k]的话,在最差情况下,时间复杂性为 O ( m * n * log(n) ),也就是10000000000左右,肯定是行不通的。

于是考虑使用平方分割,按 floor(sqrt(n))为一块,分成根号n块,然后把每一块的数字进行排序,根据查询的 i 和 j,找出 i 和 j 范围内所有的块,然后把 块未覆盖到的左边的和右边范围内放在一个 other数组里,把other数组排序。

这时相当于得到多个有序的块,然后我们需要找到这些块合并在一起后的第k大的,朴素的合并多的话,还是会很慢(合并两个有序数组为一个的操作次数,为其中大的数组的长度),所以考虑二分,可以对数组内所有的元素进行排序。

设一开始输入的数组为dat,那么我们把dat排序后作为sortedDat,然后 L = -1,R=n(二分时候的 l 和 r 是达不到的,都取开区间)当L+1 < R时循环,mid = (L+R)/2,然后我们再循环对这些块来使用二分,找到所有块中小于sortedDat[mid]的数量和cnt,如果cnt<k,则L=mid,否则R=mid,这样的话,最终的临界情况就是,所有块中,小于sortedDat[L]的元素为k-1个,小于sortedDat[L+1]的元素为k个,此时,sortedDat[L]就是当前区间里第k大的数。

考虑下复杂性 O(log(n)*sqrt(n)*log(n)*m),好像是可以的,但实际并不如此。

此时我们需要对算法进行优化,如何优化呢?考虑下比较糟糕的情况,假如n=100000,则我们会分316块,假设涉及的区间包含200块,那么每次二分,循环200次,然后200次循环里还有2分,起始也可能会比较慢,所以我们考虑如何把200个小块合并,假设2个相邻的合并成一个,改成100的话,速度可以快一倍。

于是我们可以在起初分桶的时候,按2 * floor(sqrt(n)) 来分一些大块,然后对这些块也都进行排序,一个大块相当于2个小块。

于是我们执行每次查询时需要进行的操作为:

1、根据区间 i j 找到包含的桶

2、根据找到的桶,来处理不在桶内,而且被 i 和 j 包含的区间,对此区域排序并记录长度。

3、循环包含的桶,如果 两个相邻的小桶可以被一个大桶替换的话,则用这个大桶替换掉小桶,依据此思路,把所有的小桶都用大桶替换。

4、 对sortedDat里面的数进行二分,L = -1,R=n,针对每次的mid,循环里利用二分找到所有涉及的小桶、大桶和其他区域里小于 sortedDat[mid]的数量总和cnt,如果cnt小于k,则L=mid,否则R=mid,循环结束时,输出sortedDat[L]即可

(如果不涉及到任何桶,那直接输出other[k-1]即可,不用走二分这个思路)

(然后利用大桶替换小桶时,可以循环涉及的小桶,如果它不是涉及到的最后一个,且它的下标 i是偶数,则它和它的下一个被一个大桶替换,否则的话这个桶保留,简单的思路就是定义一个变量i=0,设当前区间涉及的小桶数量为bucketLen,保存当前区间相关的桶的数组为bucket,然后 i < bucketLen时候循环,如果i+1小于bucketLen且bucket[i]%2==0,那么就把 i /2记录为当前涉及的大桶 bucketBig[bigLen++]=i/2,然后i +=2,否则就把 bucket[i]加到队列里,最后把不在队列里的小桶都去掉,把队列里的小桶记录下来为替换后涉及到的小桶,然后大桶就是bucketBig里的大桶)

(我针对桶的下标从0开始计算,然后下标 i 的小桶的区间为 [ i * 根号n , (i + 1) * 根号n),下标 i 的大桶的区间为[ 2 *  i * 根号n , (i + 1) * 2 * 根号n),这样的话大桶 i /2就代表小桶 i 和 i + 1,需要i%2==0 )

优化之后,就侥幸过了

(做完了之后看下书,发现书里使用平方分割每个桶是1000大小,不过我觉得比赛时靠自己思考很难想到扩到1000吧!我这种思路两个桶变成一个思路倒是容易想到而且可行!)

2、线段树

我做完了这个题目之后,读了下挑战程序设计,发现书中还有一种解决方法是线段树,维护数组的线段树也叫区域树,让叶子节点维护长度为1的有序数组,它的父节点维护长度为2有序数组,然后根节点维护 [ 0 , max(n_,2^i) ) 有序数组,

这个树也挺好创建的,就和线段树的初始化一样,先解决叶子节点,然后把两个叶子节点合并时候,就把小的数字放前面,大的数字放后面即可。

然后两个有序数组合并时,就可以定义两个指针 L和R,然后记录每个的长度lenL,lenR,设父数组的长度为lenPa,设父数组时arrPa,左孩子数组是arrL,右孩子数组是arrR

while L < lenL || R < lenR

     if R >= lenR

        arrPa[lenPa++] = arrL[L++]

    else if L <  lenL && arrL[L]<arrR[R]

        arrPa[lenPa++] = arrL[L++]

    else

        arrPa[lenPa++]=arrR[R++]

简单写了几行不标准的伪代码,两个有序数组合并成一个有序数组,就是这样的,这个效率上和STL和merge是差不多的,所以线段树初始化就可以用这个方法把 i * 2 +1和i*2+2的数组都合并到i的数组。

然后线段树维护这么大的一个数组,定义变量时候肯定没办法直接定义二维数组,我的思路就是定义一个 int *dat[262150],然后在初始化的时候:

1、如果是叶子节点就 dat[i]=new int[1],然后记录len[i]=1

2、如果是父节点,那就 dat[i]=new int[len[i*2 + 1] + len[i*2 + 2]]

初始化结束之后,我们针对每次查询,先查下线段树,找到的所有线段树节点,然后这些节点自己维护的数组肯定是有序的,所以思路也是和平方分割差不多。

把数组排序,二分排好序的数组里所有的元素,对 在区间内的 线段树节点数组 进行循环二分,记录小于 数组第mid项(拍好序数组里mid下标的) 的数量cnt,如果cnt<k,则L=mid,否则R=mid,临界情况就是,区间里小于L+1下标的元素是k个,小于L下标的元素的个数是k-1个,此时,L下标的元素就是区间内第k大的。

我一开始用vector,挂了很多次,因为当时没看书,不知道可以使用resize+merge,所有的元素都是使用循环push_back,结果一直TLE

后来想到可以先定义成指针数组,然后在初始化的时候使用 new int[10]这种,然后这样就可以当作int二维数组来用,放弃了vector之后侥幸过了。

后来我过了之后,看了下书里,发现书中使用了merge和resize,也使用了upper_bound,之后我又对我自己的代码进行调优了下,去掉了一次无用的初始化,然后发现其实不用vector、merge和resize,也不用upper_bound,全都自己实现就行,自己写new int[10]这种可变数组代替vector,自己写上文中的循环那种合并代替merge,自己写binarySearch去找小于number的数量代替upper_bound,也是可以过的,时间上也优化了一些,如下

三、代码

1、平方分割

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int bucket[320][320];
int bucket_1[160][640];
int dat[100009], sortedDat[100009], n, bQue[320], queLen, bQue_1[160], queLen_2, b, otherLen, other[100009];
queue<int> que;
void input()
{
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &dat[i]);
        sortedDat[i] = dat[i];
    }
    sort(sortedDat, sortedDat + n);
}
void bucketMethod()
{
    b = 1;
    while (b * b <= n)
    {
        b++;
    }
    b--;
    for (int i = 0; ((i + 1) * b) <= n; i++)
    {
        for (int j = 0; j < b; j++)
        {
            bucket[i][j] = dat[j + (i * b)];
        }
        sort(bucket[i], bucket[i] + b);
    }
    for (int i = 0; ((i + 1) * 2 * b) <= n; i++)
    {
        for (int j = 0; j < (2 * b); j++)
        {
            bucket_1[i][j] = dat[j + (i * b * 2)];
        }
        sort(bucket_1[i], bucket_1[i] + (2 * b));
    }
}
void findBucket(int L, int R)
{
    queLen = 0;
    for (int i = 0; ((i + 1) * b) <= n; i++)
    {
        if ((i * b) >= L && ((i + 1) * b) <= R)
        {
            bQue[queLen++] = i;
        }
    }
}
void handleOther(int L, int R)
{
    otherLen = 0;
    if (queLen == 0)
    {
        for (int i = L; i < R; i++)
        {
            other[otherLen++] = dat[i];
        }
    }
    else
    {
        for (int i = L; i < (bQue[0] * b); i++)
        {
            other[otherLen++] = dat[i];
        }
        for (int i = ((bQue[queLen - 1] + 1) * b); i < R; i++)
        {
            other[otherLen++] = dat[i];
        }
    }
    if (otherLen > 0)
    {
        sort(other, other + otherLen);
    }
}
void findBucketPlus()
{
    queLen_2 = 0;
    if (queLen == 0)
    {
        return;
    }
    while (!que.empty())
    {
        que.pop();
    }
    int i = 0;
    while (i < queLen)
    {
        if ((i + 1) < queLen && (bQue[i] % 2 == 0))
        {
            bQue_1[queLen_2++] = bQue[i] / 2;
            i += 2;
        }
        else
        {
            que.push(bQue[i]);
            i++;
        }
    }
    if (queLen_2 != 0)
    {
        queLen = 0;
        while (!que.empty())
        {
            int _front = que.front();
            que.pop();
            bQue[queLen++] = _front;
        }
    }
}
int binarySearch(int *arr, int len, int num)
{
    int l = -1, r = len;
    while (l + 1 < r)
    {
        int mid = (l + r) / 2;
        if (arr[mid] < num)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    return (l + 1);
}
void solve(int k)
{
    if (queLen == 0 && queLen_2 == 0)
    {
        printf("%d\n", other[k - 1]);
        return;
    }
    int l = -1, r = n;
    while (l + 1 < r)
    {
        int mid = (l + r) / 2;
        int cnt = 0;
        for (int i = 0; i < queLen; i++)
        {
            cnt = cnt + binarySearch(bucket[bQue[i]], b, sortedDat[mid]);
        }
        for (int i = 0; i < queLen_2; i++)
        {
            cnt = cnt + binarySearch(bucket_1[bQue_1[i]], 2 * b, sortedDat[mid]);
        }
        if (otherLen > 0)
        {
            cnt = cnt + binarySearch(other, otherLen, sortedDat[mid]);
        }
        if (cnt < k)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    printf("%d\n", sortedDat[l]);
}
int main()
{
    int m, i, j, k;
    while (~scanf("%d%d", &n, &m))
    {
        input();
        bucketMethod();
        while (m--)
        {
            scanf("%d%d%d", &i, &j, &k);
            findBucket(i - 1, j);
            handleOther(i - 1, j);
            findBucketPlus();
            solve(k);
        }
    }
    return 0;
}

2、线段树

#include <iostream>
#include <algorithm>
using namespace std;
int *dat[262150];
int num[100009], len[262150], n_, n, inf = 0x3f3f3f3f, nodeQue[262150], queLen, sortedNum[100009];
void input()
{
    for (int i = 0; i < n_; i++)
    {
        scanf("%d", &num[i]);
    }
}
void build()
{
    n = 1;
    while (n < n_)
    {
        n = n * 2;
    }
    for (int i = (2 * n - 2); i >= 0; i--)
    {
        if (i >= (n - 1))
        {
            dat[i] = new int[1];
            len[i] = 1;
            if ((i - n + 1) < n_)
            {
                dat[i][0] = num[i - n + 1];
            }
            else
            {
                dat[i][0] = inf;
            }
        }
        else
        {
            int pLen = 0;
            int lChild = i * 2 + 1;
            int rChild = i * 2 + 2;
            pLen = 0;
            dat[i] = new int[len[lChild] + len[rChild]];
            int lId = 0;
            int rId = 0;
            while (lId < len[lChild] || rId < len[rChild])
            {
                if (rId >= len[rChild])
                {
                    dat[i][pLen++] = dat[lChild][lId++];
                }
                else if (lId < len[lChild] && dat[lChild][lId] < dat[rChild][rId])
                {
                    dat[i][pLen++] = dat[lChild][lId++];
                }
                else
                {
                    dat[i][pLen++] = dat[rChild][rId++];
                }
            }
            len[i] = pLen;
        }
    }
    for (int i = 0; i < n_; i++)
    {
        sortedNum[i] = dat[0][i];
    }
}
void query(int l_, int r_, int i, int l, int r)
{
    if (l_ >= r || r_ <= l)
    {
    }
    else if (l >= l_ && r <= r_)
    {
        nodeQue[queLen++] = i;
    }
    else
    {
        query(l_, r_, i * 2 + 1, l, (l + r) / 2);
        query(l_, r_, i * 2 + 2, (l + r) / 2, r);
    }
}
int binarySearch(int i, int number)
{
    int l = -1, r = len[i];
    while (l + 1 < r)
    {
        int mid = (l + r) / 2;
        if (dat[i][mid] < number)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    return (l + 1);
}
void solve(int k)
{
    if (queLen == 1)
    {
        printf("%d\n", dat[nodeQue[0]][k - 1]);
        return;
    }
    int l = -1, r = n_;
    while (l + 1 < r)
    {
        int mid = (l + r) / 2;
        int cnt = 0;
        for (int i = 0; i < queLen; i++)
        {
            cnt += binarySearch(nodeQue[i], sortedNum[mid]);
        }
        if (cnt < k)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    printf("%d\n", sortedNum[l]);
}
int main()
{
    int m, i, j, k;
    scanf("%d%d", &n_, &m);
    input();
    build();
    while (m--)
    {
        scanf("%d%d%d", &i, &j, &k);
        queLen = 0;
        query(i - 1, j, 0, 0, n);
        solve(k);
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1064686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轻松实现视频、音频、文案批量合并,享受批量剪辑的便捷

在日常生活中&#xff0c;我们经常会需要将多个视频、音频和文案进行合并剪辑&#xff0c;以制作出符合我们需求的短视频。然而&#xff0c;这个过程通常需要花费大量的时间和精力。幸运的是&#xff0c;现在有一款名为“固乔智剪软件”的工具可以帮助我们轻松完成这个任务。 首…

mac怎么卸载软件没有叉的那种,别慌看这里

ac电脑对于很多人来说是一个高效、优雅的工作工具&#xff0c;但就像所有电子设备一样&#xff0c;有时候也需要进行软件的添加和删除以保持其最佳性能。然而&#xff0c;对于一些特殊类型的软件—也就是那些没有"叉"标志来直接卸载的—如何正确地从Mac上删除它们呢&…

RK3568笔记一:RKNN开发环境搭建

若该文为原创文章&#xff0c;转载请注明原文出处。 由于对AI的好奇&#xff0c;想要学习如何部署AI&#xff0c;所以从RV1126到RK3568中过渡。 一、介绍 RK3568开发板使用的是正点原子新出的ATK-DLRK3568 开发板&#xff0c;主要是学习从训练到部署的全过程&#xff0c;并记…

One Thread One Loop主从Reactor模型⾼并发服务器

One Thread One Loop主从Reactor模型⾼并发服务器 文章目录 One Thread One Loop主从Reactor模型⾼并发服务器一些补充HTTP服务器Reactor 模型eventfd通用类Any 目标功能模块划分&#xff1a;SERVER模块Buffer模块&#xff1a;编写思路&#xff1a;接口设计&#xff1a;具体实现…

Go Gin Gorm Casbin权限管理实现 - 3. 实现Gin鉴权中间件

文章目录 0. 背景1. 准备工作2. gin中间件2.1 中间件代码2.2 中间件使用2.3 测试中间件使用结果 3. 添加权限管理API3.1 获取所有用户3.2 获取所有角色组3.3 获取所有角色组的策略3.4 修改角色组策略3.5 删除角色组策略3.6 添加用户到组3.7 从组中删除用户3.8 测试API 4. 最终目…

FreeRTOS入门教程(队列的概念及相关函数介绍)

文章目录 前言一、队列概念二、队列的使用方法1.创建队列动态创建静态创建 2.复位队列3.删除队列4.写队列5.读队列6.查询队列7.覆盖/查看覆盖查看 总结 前言 本篇文章将带大家学习FreeRTOS中的队列&#xff0c;掌握什么是队列&#xff0c;并且学习如何使用队列&#xff0c;在什…

集成学习

集成学习&#xff08;Ensemble Learning) - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/27689464集成学习就是组合这里的多个弱监督模型以期得到一个更好更全面的强监督模型&#xff0c;集成学习潜在的思想是即便某一个弱分类器得到了错误的预测&#xff0c;其他的弱分类器…

Pytorch基础:Tensor的permute方法

相关阅读 Pytorch基础https://blog.csdn.net/weixin_45791458/category_12457644.html 在Pytorch中&#xff0c;permute是Tensor的一个重要方法&#xff0c;同时它也是一个torch模块中的一个函数&#xff0c;它们的语法如下所示。 Tensor.permute(*dims) → Tensor torch.perm…

PbootCMS SQL注入漏洞

漏洞复现 访问漏洞url 数据库是mysql 构造payload&#xff0c;条件为假时&#xff0c;未查到任何数据 http://x.x.x/index.php?search 1select 0页面回显 构造payload&#xff0c;条件为真时&#xff0c;查询到数据 1select1文笔生疏&#xff0c;措辞浅薄&#xff0c;望各…

邮箱注册实现(二)注册接口实现

如果邮箱地址错误或非法&#xff0c;运行时会报错。因此需要增加校验&#xff1a; Validated RestController RequestMapping("/api/auth") public class AuthorizeController {ResourceAccountService service;GetMapping("/ask-code")public RestBean&l…

typescript 类型声明文件

typescript 类型声明文件概述 在今天几乎所有的JavaScript应用都会引入许多第三方库来完成任务需求。这些第三方库不管是否是用TS编写的&#xff0c;最终都要编译成JS代码&#xff0c;才能发布给开发者使用。6我们知道是TS提供了类型&#xff0c;才有了代码提示和类型保护等机…

R实现数据分布特征的视觉化——多笔数据之间的比较

大家好&#xff0c;我是带我去滑雪&#xff01; 如果要对两笔数据或者多笔数据的分布情况进行比较&#xff0c;Q-Q图、柱状图、星形图都是非常好的选择&#xff0c;下面开始实战。 &#xff08;1&#xff09;绘制Q-Q图 首先导入数据bankwage.csv文件&#xff0c;该数据集…

[MIT6.824] Lab 3: Fault-tolerant Key/Value Service

[MIT6.824] Lab 3: Fault-tolerant Key/Value Service 目标 通过在Lab2中实现的Raft库&#xff0c;构建一个可容灾的KV数据库。 需要实现的服务有三种操作: Put(key, value) key和value都是string&#xff0c;put设置指定key的value. Append(key, arg) 将arg append到key对…

『Linux』Linux环境搭建 | 阿里云云服务器白嫖 | Xshell环境配置

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言、Linux &#x1f325;️每日语录&#xff1a;时间&#xff0c;都是公平的&#xff0c;不公平的&#xff0c;只是现在的自己&#xff0c;对未来的自己。 ❤️感谢大家点赞&#x1f44d;收…

大华城市安防系统平台任意文件下载漏洞

一、漏洞描述 大华城市安防监控系统平台是一款集视频、报警、存储、管理于一体的综合安防解决方案。该平台支持多种接入方式&#xff0c;包括网络视频、模拟视频、数字视频、IP电话、对讲机等。此外&#xff0c;该平台还支持多种报警方式&#xff0c;包括移动侦测、区域入侵、…

Geteway

大家好我是苏麟今天带来Geteway. Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单…

【C++】STL详解(十一)—— unordered_set、unordered_map的介绍及使用

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】STL…

ROS机械臂开发-开发环境搭建【一】

目录 前言环境配置docker搭建Ubuntu环境安装ROS 基础ROS文件系统 bugs 前言 想系统学习ROS&#xff0c;做一些机器人开发。因为有些基础了&#xff0c;这里随便写写记录一下。 环境配置 docker搭建Ubuntu环境 Dockerfile # 基础镜像 FROM ubuntu:18.04 # 设置变量 ENV ETC…

数据结构课程设计题目——链表综合算法设计、带头双向循环链表、插入、显示、删除、修改、排序

文章目录 链表综合算法设计——校园人事信息系统1.要求2.代码实现&#xff08;以带头双向循环链表为例&#xff09;2.1基本程序结构2.2节点和链表的初始化2.3链表的插入2.4链表的显示2.5链表的删除2.6链表的修改2.7链表的排序&#xff08;仿函数实现&#xff09; 3.全部源码 链…

Arxdbg读取cad扩展属性

xdlist可以读取CAD实体属性&#xff0c;子实体的扩展属性看不到。 下载arxdbg,appload加载。 鼠标右键即可看见&#xff0c;选Entity Info。 查看xdata,dxfdata都可以看到扩展信息。