数据离散化
定义:把无穷大集合中的若干个元素映射为有限集合以便于统计的方法。
当数据之间差值很大,即使排完序后,两个数之间仍有很大的差值,不适合直接用下标表示,这样会导致数组开的过大,容量不够,且中间有很多空没有用。针对这种情况,就想到把这间距很大的 m 个数据,在映射到 [1-m] 上,这样就会有效的减少数组的大小,且中间不会有浪费的空间。
即把稀疏的数据变的稠密起来。
步骤
1、用 lang 数组收集所有语言。
2、对 lang 数组排序、去重后保存到 uni 语言,uni 也就是语言的稠密编号。
3、find 函数用于把原始的稀疏编号转变为稠密编号。
4、ans 数组记录每种语言的科学家数。即这门语言有多少科学家会。
5、遍历所有电影,以每部电影的语音语言为条件,在ans数组中找最大值,若有多个相同的最大值,就找字幕语言最多的。
代码片段
1.原始编号的范围是$1-10^9$,数据范围过大,超出数组可以保存的最大范围,但观察到这么大的范围其实只使用了很少的一部分($10^5$ 级别),故用 uni 数组存储稠密化的语言编号。
这时,需要一个find函数,完成从稀疏编号到稠密编号的转化。
//使用库函数
int find(int x){
return lower_bound(uni+1,uni+1+k,x)-uni;
}
//也可以直接二分
int find(int x){
int l=1,r=k+1;
while(l<r){
int mid=(l+r)/2;
if(uni[mid]>=x) r=mid;
else l=mid+1;
}
return r;
}
2.统计 uni 数组中,每种语言有多少个科学家会说,并用 ans 数组记录。
a 数组存的是原始的稀疏编号,这是遍历所有科学家,得到的每个科学家会说的语言。
ans 数组存的是稠密编号,变成稠密编号主要是便于存储和便于遍历所有语言,否则过完一个语言,下个语言不知道哪里去找(每个语言的原始编号并不是连续的)。
所以,把 a 数组的稀疏编号拿到 find 函数里面过一下,就变成 ans 数组需要的稠密编号。
3.如果出现当所有的电影的声音和字幕语言,所有的科学家都不懂的极端情况,随便选一个(以前没有数据加强后出现这个极端数据)。
for(int i=1;i<=n;i++) ans[find(a[i])]++;
完整代码
#include <cstdio>
#include <algorithm>
using namespace std;
int n,m,k=0,tot=0;
const int N=2e5+50;
//3*N是因为语言的来源有3个地方,假设都不相同,则有3*N种语言
int lang[3*N],uni[3*N],a[N],b[N],c[N],ans[3*N];
//find作用是把稀疏编号转为稠密编号
int find(int x){
return lower_bound(uni+1,uni+1+k,x)-uni;
}
int main(){
//保存科学家会的语言,并用lang记录
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
lang[++tot]=a[i];
}
//保存电影音频的语言,并用lang记录
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d",&b[i]);
lang[++tot]=b[i];
}
//保存电影字幕的语言,并用lang记录
for(int i=1;i<=m;i++){
scanf("%d",&c[i]);
lang[++tot]=c[i];
}
//排序lang,为了去重复
sort(lang+1,lang+1+tot);
//把lang数组去重复,保存到uni数组
//uni的数组下标做为每种语言(原有的1-10^9的稀疏编号)新的稠密编号
for(int i=1;i<=tot;i++){
if(i==1 || lang[i]!=lang[i-1]){
uni[++k]=lang[i];
}
}
//a[i]中保存原始的稀疏编号,用find转变成稠密编号,并用ans数组记录每种语言出现的次数。
for(int i=1;i<=n;i++) ans[find(a[i])]++;
//遍历所有电影,按要求找到最多科学家会的电影
int ans0,ans1,ans2;
//ans0保存最终结果,ans1和ans2为中间结果
ans0=ans1=ans2=0;
for(int i=1;i<=m;i++){
//算出第i个电影音频语言的科学家数,和第i个字幕语言的科学家数
int anx=ans[find(b[i])],any=ans[find(c[i])];
//如果ans大于ans1或者前者相等且any大于ans2时,更新
if(anx>ans1 || (anx==ans1 && any>ans2)){
ans0=i,ans1=anx,ans2=any;
}
}
//如果所有的电影的声音和字幕的语言,科学家们都不懂,随便选一个
if(ans0==0){
printf("%d\n",1);
}else{
printf("%d\n",ans0);
}
return 0;
}
2003ms
大佬您写的代码非常好,但我有一个疑问,我把ma换成普通数组就过不了了,为什么ma是map才能过?
我明白了数组要开1e9会炸,但为什么map没事呢?没定义大小?用多少消耗多少内存?
map占用的内存是取决于你赋值过的空间,简单点就是说如
ma[a[i]]++;
这样的就会使得多用一个单位(如int是4byte)的内存,map主要是查询需要的时间多一点,查询到一个元素需要使用的时间是 $/mathcol O(NlogN)$ 的。具体可以上网搜索map的用法太妙了佬orz
我怎么不过?
#include [HTML_REMOVED]
int n,m,k;
const int MAXN = 2e5 +50;
int a[MAXN],b[MAXN],c[MAXN];
int uni[MAXN],count[MAXN];
int query(int x){
int pos = std::lower_bound(uni+1,uni+1+k,x) - uni;
return uni[pos] == x ? pos : 0;
}
inline long long read(){
long long d = 0;signed char w = 1;/符号位最好不要用char来表示 因为char可能实现是unsigned char 这时候用于表示符号机会出错 不过我们可以显式的指明我们要的哪个char/
char ch = getchar();
while(ch > ‘9’ || ch < ‘0’){
if(ch == ‘-‘) w = -1;
ch = getchar();
}
while(‘0’ <= ch && ch <= ‘9’){
d = d10 + (ch^48);
ch = getchar();
}
return d*w;
}
int main(){
n =read();
for(int i = 1;i<=n;i){
a[i] = read();
uni[i] = a[i];
}
m = read();
for(int i = 1;i<=m;i) b[i] = read();
for(int i = 1;i<=m;++i) c[i] = read();
}
666,好题解,好久没见到离散化了
%%%,没有y总视频讲解就靠大佬们的题解了
大佬,为啥用你的代码不能ac呀
扫了眼以后,我写了个大致思路相同的,最后一个测试用例 n=200000 一开始也是不能过,但是把上限 N 从 200000 改成 200500 就过了(改成 200001、200050等都不行),有点不明所以。明明压根用不到 200000 以后的空间了。
我测了下这个题解,能过18/21,我自己写的一开始是20/21…
你可以试试改改数组上限 N
谢谢提醒,已经修改。原因是数据加强了,增加了一种极端情况下的数据:如果出现当所有的电影的声音和字幕语言,所有的科学家都不懂的极端情况,随便选一个。
和N应该没关系,参考上面的解释再试试吧。
遇到过这个错误,其实是ans数组范围开的不够大,导致部分find(b[i])、find(c[i])的下标超出了ans的范围。ans的范围应该包含所有语言2*m+n。
声明时ans[N * 3];
增加N的上限就能过,可能是恰好满足find的索引在N的范围内,遇到其他数据还是会发生越界。
写法很棒棒
谢谢大佬
我用VECTOR来实现unique数组,最后大数据的时候就出问题了,也不知道咋回事。
你可以在“AcWing 802. 区间和”这道题里,看下y老师的代码,也是用vector写的。
谢
ans0可以为0吧,因为你的for循环是从1开始的
附议
嗯,也对
大佬,牛逼