位图法

电话号码问题

商业单位需要容易记忆的电话号码,有一些方法可以让电话号码变得更容易记忆。譬如,可以把电话号码写成单词或短语,如 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");
}


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值