位图法

电话号码问题

商业单位需要容易记忆的电话号码,有一些方法可以让电话号码变得更容易记忆。譬如,可以把电话号码写成单词或短语,如 MON-GLOP 可以代表滑铁卢大学的电话。有时仅仅是把号码的一部分写成单词,如打 310-GINO 便可向 GINO 比萨饼店定购比萨。另一种让电话号码容易记忆的方法是将数字用一种容易记的方式组合起来,譬如 3-10-10-10 也可以代表 GINO 比萨饼店。

电话号码的标准形式是七位十进制数字,在它的第三位和第四位之间用连字符连接(例如:666-1200)。电话的键盘提供了字符与数字之间的映射关系,如下所示:

2A、B和C 
3D、E和F 
4G、H和I 
5J、K和L 
6M、N和O 
7P、R和S 
8T、U和V 
9W、X和Y 

Q 和 Z 没有映射到键盘,而连字符不需要被拨打并且可以根据需要添加和删除。MON-GLOP 的标准形式是 666-4567,310-GINO 的标准形式是310-4466,3-10-10-10的标准形式也是 310-1010。

如果两个电话号码有相同的标准形式,那么这两个电话号码是相同的。

你所在的公司正在编辑一本当地商业单位的电话簿,作为质量控制流程的一部分,你需要确认在该电话簿中有没有错误的电话号码,以及有没有两个(或两个以上的)商业单位使用相同的电话号码。由于当地只使用了 3 和 6 两个区段,因此电话号码的第一个数字应当永远是 3 或者 6,如果出现了其它数字,就表示这个电话号码错了。此外,如果电话号码中出现了 Q 和 Z,也说明这个电话错了。

输入

一次输入为一个样例。每个号码一行,每行的字符不会超过 20 个。每次输入的数据可能会非常大,譬如超过 1,000,000 个电话号码。

你可以假设输入中可能会出现重复的电话号码不超过 1,500 个,每个号码重复的次数不超过 1000 次。

输出

输出包括两个部分,第一个部分是错误的电话号码,对于这些号码应当按照输入的顺序以原始的形式输出。在输出错误电话号码前输出“Error:”,随后输出这些号码,如果没有错误的电话号码,则输出“Not found.”。

第二部分是重复的电话号码,对每一个在电话簿中以任何形式出现一次以上的电话号码,生成一行输出。这一行应以标准形式给出电话号码,其后跟随一个空格,空格后跟随电话号码在电话簿中出现的次数。所有重复的电话号码输出行应以号码的升序排列(小号码在前)。在输出重复电话号码前输出“Duplication”,随后按照上述格式输出号码,如果在输入中没有重复的电话号码,则输出:“Not found.”。

注意

你所编写的程序以后可能会在一种特殊的嵌入式设备上运行,为了降低成本,这种设备使用的 CPU 不是很快、可用的 RAM 为 288K(跟 GBA 一样)且它没有磁盘设备因此不能使用文件作为数据的临时存储。

提示

请参考《编程珠玑》第一部分,若程序不能在规定的内存中运行,则不得分。

  测试输入关于“测试输入”的帮助 期待的输出关于“期待的输出”的帮助 时间限制关于“时间限制”的帮助 内存限制关于“内存限制”的帮助 额外进程关于“{$a} 个额外进程”的帮助
测试用例 1 以文本方式显示
  1. 4873279↵
  2. ITS-EASY↵
  3. 666-4567↵
  4. 3-10-10-10↵
  5. 666-GLOP↵
  6. MON-GLOP↵
  7. 367-11-11↵
  8. 310-GINO↵
  9. F101010↵
  10. 666-1200↵
  11. -4-8-7-3-2-7-9↵
  12. 487-3279↵
以文本方式显示
  1. Error:↵
  2. 4873279↵
  3. ITS-EASY↵
  4. -4-8-7-3-2-7-9↵
  5. 487-3279↵
  6. Duplication:↵
  7. 310-1010 2↵
  8. 666-4567 3↵
1秒 512KB 0
测试用例 2 以文本方式显示
  1. 3456789↵
以文本方式显示
  1. Error:↵
  2. Not found.↵
  3. Duplication:↵
  4. Not found.↵
1秒 512KB 0
用了位图法,很漂亮的算法。
贴一下学长的解析,留作以后复习用:

 《编程珠玑》这本书提到了可以用位图法在有限的内存下存储海量数据,这篇是来说位图法是怎么用的

  首先一些基本概念,一个char型1个字节,一个int型,一般4个字节。

  一个字节有8个位,即8个bit。

  现在给了512个kb,即有512*1024个字节,即50W+的字节,然后每个字节又有8个位,那么一共有400W+个位

  现在有300W和600W两个区间,一共两百万个数对吧=w=。

  如果单纯地用一个整型来存的话,一个int只能存一个数字而已,但一个int就要用4个字节,50W+的字节也只能存10W+的数字而已,这点数字太少了QAQ……所以就要利用到位了

  思想如下:每个字节有8个位,就用它来代表8个数

  这样,200W+的数,只需要25W+的字节就够了,就开一个char save[25W]。

  一开始,把所有字节里的每一个位都变成0,用一个循环,使每个字节都做一下save[x]=save[x]&0;就行,通过这个位操作,能使每一个字节的8个位都变成0;

比如数字5,把它放到第0个字节里,但具体放在哪个位里呢?

既然是数字5,就从这个字节从最右开始向左数5位,放在那里吧。

通过把那个位的值变成1的方法来表示,这个字节的这一个位已经有一个数了。

(从最右开始是因为,我可以直接用(1<<5)表示把1向左移动5位,再把save[当前字节]并上(1<<5)的值,就能把那个位的值变成1,比较方便)

  这样,就把5存在了save[0]这个字节的右起第5位(注意,实际是第6位,因为我们是从第0位开始记数的)上了;

  其实这和一般的flag[]数组的思想是一样的,如果第5个数存在,就令flag[5]=1;这里只不过是进入到一个字节,把字节的某一个位(bit)的值通过位操作的方法变为1而已

那么看下一个数,比如13,13肯定不能放第0个字节里了,因为那个字节里会放0~7这8个数,13/8=1,把13放第1号字节里

  现在看13要放第1个字节里的哪个位?

  令13对8取余,13%8=5,就把13放在第1个字节里的第5位,把这个位置的值变为1,同上面一样,把当前值并上(1<<5),save[1]=save[1]|(1<<5)即可。

  所以,我们能总结出这样一个模型

  假设当前数字是num, 它会放在第byte_position这个位置,具体位置在save[byte_position]的第bit_position位……

  (名字好长……接下来byte_position用bytep表示,bit_position用bitp表示了)

  那么,bytep等于num/8的值,而bitp就是num%8的值(不知道为什么?再看看13和5的放法吧QvQ)

 

  由于300W和600W这些数字都比较大,而空间有限,直接用300W/8会导致之前100W、200W的空间浪费,所以遇到300W的,把它们对100取模,变成100W内的数,遇到600W的,取模再加上100W,变成一个100W的数,再考虑把新的数放到哪个位置

 

  好了,现在已经放好了一个数字了XD,那么如果判断某个数是不是存在呢?

  比如,13,13/8=1,13%8=5,意味着这个数如果存在,那第1个字节的第5个位的值应该为1的,仍然是利用位操作

  把save[1]和1左移5位后的值,即(1<<5),即二进制状态下的(00100000)做与(&)操作,如果那个位上为1,&后得到的值肯定不会是0,如果那个位上是0,&后就只能是0了

  所以,我们要判断一下save[1]&(1<<5)的值,如果为1,就代表之前存在过了,这时候要做什么好呢=w=,赶紧存起来呀,比如存到一个叫duplication[]的结构数组里呀,等一下要用来输出的QvQ

  如果这个值为0,那么意味着没有存在过,这时候又要做什么好呢=w=,快把这个位的值变成1呀Σ(っ °Д °;)っ

 

  好了,位图法就是这样了(⊙_⊙) ,其它的处理应该比较轻松啦>.<

本人代码:
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

char p[250010];
char t[21];
struct node
{
	int shu,geshu;
}he[1510];

int cmp(const void *a,const void *b)
{
	return ((struct node *)a)->shu - ((struct node *)b)->shu;
}
int main()
{
	int i,j,k;
	char ch;
	for(i=0;i<250000;i++)
		p[i]=p[i]&0;
	printf("Error:\n");
	int a[27]={2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,0,7,7,8,8,8,9,9,9,0};
	int fyc=0,ans=0;
	while(scanf("%s",t)!=EOF)
	{
		int len=strlen(t);
		int flag=1,temp=0;
		int hefa=0;
		for(i=0;i<len;i++)
		{
			if(t[i]<='9'&&t[i]>='0'){
				if((t[i]!='3'&&t[i]!='6') && flag==1)
				{
					hefa=1;
					break;
				}
				else
				{
					temp=(t[i]-'0')+temp*10;
					flag++;
				}
			}
			else if(t[i]<='Z'&&t[i]>='A')
			{
				if(t[i]=='Q'||t[i]=='Z')
				{
					hefa=1;
					break;
				}
				else if((a[t[i]-'A']!=3&&a[t[i]-'A']!=6)&&flag==1)
				{
					hefa=1;
					break;
				}
				else
				{
					temp=a[t[i]-'A']+temp*10;
					flag++;
				}
			}
		}
		if(hefa==1){
			printf("%s\n",t);
			fyc=1;
		}
		else if(flag!=8)
		{
			printf("%s\n",t);
			fyc=1;
		}
		else
		{
			int t1,t2;
			int fff=temp/1000000;
			if(fff==3)
				t1=temp/8-375000;
			else
				t1=temp/8-625000;
			t2=temp%8;
			if((p[t1]&(1<<t2))==0)
				p[t1]=p[t1]|(1<<t2);
			else
			{
				int ff=1;
				for(i=0;i<ans;i++)
					if(temp==he[i].shu){
						he[i].geshu++;
						ff=0;
						break;
					}
				if(ff==1){
					he[ans].shu=temp;
					he[ans++].geshu=2;
				}
			}
		}
	}
	if(fyc==0)
		printf("Not found.\n");
	printf("\n");
	printf("Duplication:\n");
	if(ans==0)
		printf("Not found.\n");
	else
	{
		qsort(he,ans,sizeof(he[0]),cmp);
		for(i=0;i<ans;i++)
		{
			int t3=he[i].shu/10000;
			int t4=he[i].shu%10000;
			printf("%d-%04d %d\n",t3,t4,he[i].geshu);
		}
	}
	system("pause");
}


<think>好的,用户现在问的是“什么是位图法”。我需要先回想一下相关知识。位图法,英文应该是Bitmap,也就是用位(bit)来表示数据状态的方法。通常用于高效存储和操作布尔类型的数据,比如存在或不存在,真或假等等。 首先,用户可能是在学习数据结构或者算法优化相关内容,或者是遇到需要处理大量二元状态的问题,比如资源分配、权限管理、去重等场景。他们可能希望了解位图法的基本概念、应用场景以及优缺点,或者具体的实现方式。 接下来要确定用户的需求层次。用户可能是个学生,或者刚入门的开发者,需要基础的解释,也可能是有一定经验,想深入优化某个应用。根据之前的对话历史,用户之前问过TLB,属于计算机系统层面的问题,所以可能对底层实现比较感兴趣,但这次的问题更偏向数据结构和算法。 需要结构化回答。先定义位图法,说明其核心思想。然后举几个典型应用场景,比如操作系统的磁盘块管理、布隆过滤器、权限控制等。接着可能需要讲实现方式,如何用位数组表示数据,以及具体的操作,如设置位、清除位、检测位。还可以提到位运算的高效性,比如空间节省和快速操作。 要注意可能的误区,比如位图法适用于数据范围较小且密集的情况,如果数据范围很大但稀疏,可能会浪费空间。这时候可以提到压缩位图如Roaring Bitmap作为扩展。同时,需要给出一个具体的例子,比如用位图法统计1到1000中出现的数字,这样用户更容易理解。 另外,用户可能关心位图法的优缺点,需要明确列出,比如优点节省空间、操作高效,缺点是不适合稀疏数据或范围过大的情况。还可以提到实际应用中的案例,比如Redis的位图操作,或者数据库中的位图索引,这样用户能联系到实际技术。 最后,总结位图法的适用场景和注意事项,帮助用户判断是否适合他们的问题。需要确保语言通俗易懂,避免过于专业的术语,但也要准确。检查是否有遗漏的重要点,比如位操作的具体实现方式,或者与其他数据结构的比较。</think>--- ### **什么是位图法?** **位图法**(Bitmap)是一种利用二进制位(bit)的0/1状态来表示数据存在性或属性的技术,核心思想是**用最小的存储空间高效管理大量布尔型数据**。它通过将数据映射到位数组中的特定位置,实现快速查询、标记和统计操作。 --- #### **核心原理** 1. **位与数据的映射** - 每个二进制位(bit)对应一个数据的状态: - `1`表示存在/满足条件(如资源被占用、元素存在)。 - `0`表示不存在/未满足条件(如资源空闲、元素缺失)。 - **示例**:用位图记录数字`1~1000`是否出现: $$ \text{位图} = [b_0, b_1, \dots, b_{999}], \quad b_i=1 \iff \text{数字}i+1\text{出现过} $$ 2. **存储优化** - 传统布尔数组每个元素占用1字节(8位),而位图法每个元素仅需1位,**空间节省8倍**。 - 例如,记录100万个数据的存在性: $$ \text{位图大小} = \frac{10^6}{8} = 125\,\text{KB} $$ --- #### **典型应用场景** 1. **资源管理** - **操作系统**:跟踪磁盘块、内存页的分配状态。 - **数据库**:标记记录是否删除或满足查询条件。 2. **去重与过滤** - **布隆过滤器**(Bloom Filter):通过多个哈希函数和位图快速判断元素是否存在(可能有误判)。 - **唯一性校验**:统计海量数据中重复项(如日志去重)。 3. **权限控制** - 用位掩码表示权限组合(如Linux文件权限`rwx`对应3位二进制)。 --- #### **具体操作实现** 1. **位图初始化** - 分配连续内存空间,所有位初始化为0。 - **示例(C语言)**: ```c uint8_t bitmap[125000]; // 1,000,000位 ≈ 125,000字节 ``` 2. **设置位(标记存在)** - 计算目标位所在的字节和偏移量,通过位运算设置。 - **公式**: $$ \text{字节位置} = \frac{\text{数据值}}{8}, \quad \text{位偏移} = \text{数据值} \mod 8 $$ $$ \text{bitmap[字节位置]} \mid= (1 << \text{位偏移}) $$ 3. **清除位(标记不存在)** - 使用位与运算清除特定位: $$ \text{bitmap[字节位置]} \&= \sim(1 << \text{位偏移}) $$ 4. **查询位(判断存在性)** - 检查特定位是否为1: $$ (\text{bitmap[字节位置]} \& (1 << \text{位偏移})) \neq 0 $$ --- #### **优缺点分析** | **优点** | **缺点** | |------------------------------|-----------------------------| | **空间效率极高** | **数据范围受限** | | 操作复杂度为$O(1)$(位运算) | 稀疏数据时可能浪费空间 | | 适合高频次小范围数据操作 | 需处理整数映射(非直接支持字符串) | --- #### **优化与扩展** 1. **压缩位图** - 对稀疏数据使用**Roaring Bitmap**,结合数组与位图,兼顾空间和速度。 - **示例**:将数据分块,稀疏块用数组存储,密集块用位图存储。 2. **分布式位图** - 超大规模数据时,分片存储在多台机器的内存中(如Redis Cluster的位图操作)。 3. **位图索引** - 数据库中为低基数列(如性别、状态)建立位图索引,加速多条件查询。 --- #### **实际案例** - **场景**:统计10亿用户中活跃用户数(每日登录记为1)。 - **位图法**:仅需约120MB内存($10^9/8 \approx 125\,\text{MB}$),遍历位图统计1的数量即可。 - **对比传统方法**:用数组存储用户ID需数GB内存。 --- #### **总结** 位图法通过极简的二进制位映射,在资源管理、去重、过滤等场景中展现出高效的空间和时间性能。其核心价值在于**用最小的代价解决大规模二元状态问题**,但需注意数据范围和稀疏性对实际效果的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值