HDOJ-4006/(大连网赛1006)- The kth great number 剖析

2024-03-20 03:32

本文主要是介绍HDOJ-4006/(大连网赛1006)- The kth great number 剖析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文不想废话,直接上多种做法。

题意:固定的k,动态加点,动态询问第k大数。

一、树状数组+二分

这里有两种做法,一种是二分sum(i),另一种是利用二进制二分逼近k。

树状数组常用来处理区间点的统计情况,这里n没有规定大小(理论上是int32),但是操作次数n是小于1000,000的,所以可以先进行离散化来储存1000,000个点值(我不知道这是不是所谓的离散化,因为点本身是整数,但是,这至少是一种散列、一种映射,异曲同工的)。

具体操作为,先读入样例中出现的所有点,然后排序,insert( lower_bound(list, list+n, a[i])-list+1 ) 。a[]为原始数列,list为排序后数列,+1为了避免下标为0。至此便完成了离散化。

返回第k大数:

① 二分sum(i),利用二分查找的思想,目标为 tot-sum(ans) < k <= tot-sum(ans-1)。sum()为树状数组的求和操作。要注意的是,树状数组中c[x]记录的是x的权值,也就是有c[x]个数同为x,则在查找第k大数的时候不一定是正好的,不能按原始的二分查找的模式,这里WA了好多次......

② 二进制二分逼近,本来是受以下二者启发,
http://tieba.baidu.com/f?kz=1033300126
http://www.cnblogs.com/zgmf_x20a/archive/2008/11/15/1334109.html

用来求第k小数的,显然已知总点数的情况下可以用来求得第k大数。

但是后来发现第一种的版本是错的,

代码类似

	int ans=0;for(int i=1<<log2(all); i; i>>=1){if(c[ans+i] <= k){k-=c[ans+=i];}}return ans;

我因为这个WA了好久,有两个错误,①改成 if(c[ans+i] < k),最后return ans+1。理由同上面红字。 ② if 中还要加上 ans+i<all &&...


代码(两种方法合在一起,见注释):

//hdoj-4006,动态求第k大数
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;#define MAXN 1000005
#define LOWBIT(x) x&(-x)
int a[MAXN];		//原数组,最多1000000个数
int list[MAXN];	//排序数列
int q[MAXN];		//是否询问:Q
int c[MAXN];
int n, k, all;int log2(int x) { int i=0; for(; x; x>>=1, i++); return i-1; }
void insert(int x)
{while(x<=all){c[x]+=1;x+=LOWBIT(x);}
}
/// 方法一:二进制,二分逼近
int find_kth_small(int k)
{int ans=0;for(int i=1<<log2(all); i; i>>=1){if(ans+i<all && c[ans+i] < k)				//!!!{k-=c[ans+=i];}}return ans+1;/*													//这个一样int cnt=0, ans=0;for(int i=1<<log2(all); i; i>>=1){ans+=i;if(ans>=all || cnt+c[ans]>=k) ans-=i;else cnt+=c[ans];}return ans+1;*/
}
int find_kth_great(int k, int tot)
{return find_kth_small(tot-k+1);
}
/// 方法二:二分sum(i)
int sum(int i)
{int res=0;for(; i>0; i-=LOWBIT(i)){res+=c[i];}return res;
}int search(int k)			//!!目标是 tot-sum(ans) < k <= tot-sum(ans-1),而普通的二分查找目标是k=a[ans].
{int l=1, r=all;int tot = sum(r);while(l<=r){int mid = (l+r)/2;if(tot-sum(mid)>=k){l = mid+1;}else if(tot-sum(mid)<k && k<=tot-sum(mid-1)){return mid;}else{r = mid-1;}}/*													//这个也是对的,但是我感觉上面的好理解while(l<=r){int mid = (l+r)/2;if(tot-sum(mid-1)>=k) l=mid+1;else r = mid-1;}return l-1;*/
}int main()
{while(cin>>n>>k){memset(c, 0, sizeof(c));memset(q, 0, sizeof(q));all = 0;									//记录总共加入的数for(int i=0; i<n; i++){char t[2];cin>>t;if(t[0] == 'I'){int x; cin>>x;a[i] = x;					//a[] 原数列list[all++] = x;		//加入的数列,之后要经过排序,方便散列}else if(t[0] == 'Q'){q[i] = 1;}}sort(list, list+all);int tot=0;for(int i=0; i<n; i++){if(!q[i]){tot++;insert(lower_bound(list, list+all, a[i])-list+1);		//+1,避免0下标}else{
//				cout<<list[search(k)-1]<<endl;		//第一种,二进制二分逼近cout<<list[find_kth_great(k, tot)-1]<<endl; //第二种,二分sum(i)}}}
}


二、建堆

做一个小顶堆,放k个元素。每insert一个数,如果此数大于堆顶,就插入堆,并去掉原堆顶,如果小于则不操作,因为无影响。这个就是利用了这个题目的特殊性,即一个样例中k是固定不变的,不像其他题目可能会输入"Q k"这样作为一条指令。也因此造就了本场比赛第一水题。(掩面.....)

众所周知,优先队列(priority_queue)是用二叉堆实现的,下面给出STL优先队列的版本:

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
struct cmp
{bool operator() (int a, int b) {return a<b? 0: 1;}
};
priority_queue<int, vector<int>, cmp> q;		//或者直接使用,greater<int>
int n, k;int main()
{while(cin>>n>>k){while(n--){char t[2];	cin>>t;if(t[0] == 'I'){int x; cin>>x;if(q.size()<k) q.push(x);else if(x > q.top()) {q.pop(); q.push(x); }}else{cout<<q.top()<<endl;}}}
}

三、

待续...


这篇关于HDOJ-4006/(大连网赛1006)- The kth great number 剖析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/828155

相关文章

usaco 1.2 Name That Number(数字字母转化)

巧妙的利用code[b[0]-'A'] 将字符ABC...Z转换为数字 需要注意的是重新开一个数组 c [ ] 存储字符串 应人为的在末尾附上 ‘ \ 0 ’ 详见代码: /*ID: who jayLANG: C++TASK: namenum*/#include<stdio.h>#include<string.h>int main(){FILE *fin = fopen (

题目1380:lucky number

题目1380:lucky number 时间限制:3 秒 内存限制:3 兆 特殊判题:否 提交:2839 解决:300 题目描述: 每个人有自己的lucky number,小A也一样。不过他的lucky number定义不一样。他认为一个序列中某些数出现的次数为n的话,都是他的lucky number。但是,现在这个序列很大,他无法快速找到所有lucky number。既然

Jenkins 通过 Version Number Plugin 自动生成和管理构建的版本号

步骤 1:安装 Version Number Plugin 登录 Jenkins 的管理界面。进入 “Manage Jenkins” -> “Manage Plugins”。在 “Available” 选项卡中搜索 “Version Number Plugin”。选中并安装插件,完成后可能需要重启 Jenkins。 步骤 2:配置版本号生成 打开项目配置页面。在下方找到 “Build Env

深度剖析AI情感陪伴类产品及典型应用 Character.ai

前段时间AI圈内C.AI的受够风波可谓是让大家都丈二摸不着头脑,连C.AI这种行业top应用都要找谋生方法了!投资人摸不着头脑,用户们更摸不着头脑。在这之前断断续续玩了一下这款产品,这次也是乘着这个风波,除了了解一下为什么这么厉害的创始人 Noam Shazeer 也要另寻他路,以及产品本身的发展阶段和情况! 什么是Character.ai? Character.ai官网:https://

最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)

文章目录 一、自动配置概念二、半自动配置(误~🙏🙏)三、源码分析1、验证DispatcherServlet的自动配置2、源码分析入口@SpringBootApplication3、@SpringBootConfiguration的@Configuration4、@EnableAutoConfiguration的@AutoConfigurationPackage和@Import5、Auto

C语言深度剖析--不定期更新的第四弹

哈哈哈哈哈哈,今天一天两更! void关键字 void关键字不能用来定义变量,原因是void本身就被编译器解释为空类型,编译器强制地不允许定义变量 定义变量的本质是:开辟空间 而void 作为空类型,理论上不应该开辟空间(针对编译器而言),即使开辟了空间,也只是作为一个占位符看待(针对Linux来说) 所以,既然无法开辟空间,也无法作为正常变量使用,既然无法使用,干脆编译器不让它编译变

Java CAS 原理剖析

在Java并发中,我们最初接触的应该就是synchronized关键字了,但是synchronized属于重量级锁,很多时候会引起性能问题,volatile也是个不错的选择,但是volatile不能保证原子性,只能在某些场合下使用。   像synchronized这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用,除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正

【Hdu】Minimum Inversion Number(逆序,线段树)

利用线段树在nlogn的时间复杂度内求一段数的逆序。 由于给的序列是由0 ~ n -1组成的,求出初始的逆序之后可以递推出移动之后的逆序数。 #include<cstdio>#include<iostream>#include<cstring>#include<algorithm>using namespace std;typedef long long LL;const in

【JavaScript】基本数据类型与引用数据类型区别(及为什么String、Boolean、Number基本数据类型会有属性和方法?)

基本数据类型   JavaScript基本数据类型包括:undefined、null、number、boolean、string。基本数据类型是按值访问的,就是说我们可以操作保存在变量中的实际的值。 1)基本数据类型的值是不可变的 任何方法都无法改变一个基本类型的值,比如一个字符串: var name = "change";name.substr();//hangconsole.log

STL源码剖析之【二分查找】

ForwardIter lower_bound(ForwardIter first, ForwardIter last,const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。      ForwardIter upper_bound(ForwardIter first, ForwardIter last, const _Tp& val