USACO:2.2.1 Preface Numbering 序言页码
一、题目描述
★Preface Numbering 序言页码
一类书的序言是以罗马数字标页码的.传统罗马数字用单个字母表示特定的数值,一下是标准数字
表:
I 1 L 50 M 1000
V 5 C 100
X 10 D 500
最多3 个可以表示为10n 的数字(I,X,C,M)可以连续放在一起,表示它们的和:
III=3
CCC=300
可表示为5x10n 的字符(V,L,D)从不连续出现.
除了下一个规则,一般来说,字符以递减的顺序接连出现:
CCLXVIII = 100+100+50+10+5+1+1+1 = 268
有时,一个可表示为10^n 的数出现在一个比它大的数前(I 在V 或X 前面,X 在L 或C 前面,等等).
在这种情况下,数值等于后面的那个数减去前面的那个数:
IV = 4
IX = 9
XL = 40
像XD, IC, 和XM 这样的表达是非法的,因为前面的数比后面的数小太多.对于XD(490 的错误表达),
可以写成 CDXC; 对于IC(99 的错误表达),可以写成XCIX; 对于XM(990 的错误表达),可以写成
CMXC.
给定N(1 <= N < 3,500), 序言的页码数,请统计在第1 页到第N 也中,有几个I 出现,几个V 出现,
等等 (从小到大的顺序).不要输出并没有出现过的字符.
比如N = 5, 那么页码数为: I, II, III, IV, V. 总共有7 个I 出现,2 个V 出现.
PROGRAM NAME: preface
INPUT FORMAT
一个整数N.
SAMPLE INPUT(preface.in)
5
OUTPUT FORMAT
每行一个字符和一个数字k,表示这个字符出现了k 次.字符必须按数字表中的递增顺序输出.
SAMPLE OUTPUT(preface.out)
I 7
V 2
二、解题思路
首先我们都能想到的就是枚举,枚举一般来说比较简单,思路清晰。没有什么复杂的算法和数据结构。
我们仔细分析题目,发现如下规律:(分析找规律的建模过程很重要!)
标准数字表:
I 1 L 50 M 1000
V 5 C 100
X 10 D 500
可建一个表存储,ch[8]={‘I‘,‘V‘,‘X‘,‘L‘,‘C‘,‘D‘,‘M‘}
分析得出,对应个、十、百和千位的数字表
(‘‘,‘I‘,‘II‘,‘III‘,‘IV‘,‘V‘,‘VI‘,‘VII‘,‘VIII‘,‘IX‘),//个位,0,1,2,...,9
(‘‘,‘X‘,‘XX‘,‘XXX‘,‘XL‘,‘L‘,‘LX‘,‘LXX‘,‘LXXX‘,‘XC‘),//十
(‘‘,‘C‘,‘CC‘,‘CCC‘,‘CD‘,‘D‘,‘DC‘,‘DCC‘,‘DCCC‘,‘CM‘),//百
(‘‘,‘M‘,‘MM‘,‘MMM‘,‘‘,‘‘,‘‘,‘‘,‘‘,‘‘));//千
通过查看上表,排列相当有规律。从个位到千位,不仅形式没变,而且字符变化是标准数字表中字符位置的相同偏移递增。因此,我们可以利用这个特征来简化枚举的操作,对每个数字从个位到千位,通过字符偏移量进行查表,逐位进行字符计数。
我的程序实现如下:
#include <iostream> #include <cstdio> using namespace std; int N; int vis[7]; char c[8]={'I','V','X','L','C','D','M'}; // I 1 L 50 M 1000 // V 5 C 100 // X 10 D 500 // ('','I','II','III','IV','V','VI','VII','VIII','IX'),//个 // ('','X','XX','XXX','XL','L','LX','LXX','LXXX','XC'),//十 // ('','C','CC','CCC','CD','D','DC','DCC','DCCC','CM'),//百 // ('','M','MM','MMM','','','','','',''));//千 void cnt(int x,int digit){ //个位 //if(digit==1){ int var; var=2*(digit-1);//对应个、十、百和千数位的数字表相对位移 if (1<=x&&x<=3){ vis[0+var]=vis[0+var]+x;} else if(x==4){ vis[0+var]=vis[0+var]+1; vis[1+var]=vis[1+var]+1; } else if(x==5){ vis[1+var]=vis[1+var]+1; } else if (6<=x&&x<=8){ vis[0+var]=vis[0+var]+x-5; vis[1+var]=vis[1+var]+1; } else if (x==9){ vis[0+var]=vis[0+var]+1; vis[2+var]=vis[2+var]+1; } } void dfs(int N,int digit){ int num=N%10; if (num!=0){ //&& (num%10)!=0 cnt (num,digit);} if(N/10!=0){ N=N/10; dfs(N,digit+1); } } int main(){ freopen("preface.in","r",stdin); freopen("preface.out","w",stdout)p; int i; cin>>N; for(i=0;i<=N;i++) dfs(i,1); for (i=0;i<7;i++) { if (vis[i]!=0) cout<<c[i]<<" "<<vis[i]<<endl; } return 0; } //USACO参考答案 // We use a lookup table called "encode" to encode each digit, translating from the letters // for the ones place to the letters for the place that we care about. // The "romandigit" function takes care of each digit, and the "roman" function strings them all together. /* PROG: preface ID: rsc001 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> static char *encode[] = { //编码,指针字符数组,已初始化 "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", }; char* romandigit(int d, char *ivx) //返还字符指针,指向一个字符 { char *s, *p; static char str[10]; for(s=encode[d%10], p=str; *s; s++, p++) { switch(*s){ case 'I': *p = ivx[0]; break; case 'V': *p = ivx[1]; break; case 'X': *p = ivx[2]; break; } } *p = '\0'; return str; } char* roman(int n) { static char buf[20]; strcpy(buf, ""); strcat(buf, romandigit(n/1000, "M"));//拼接1000 strcat(buf, romandigit(n/100, "CDM")); strcat(buf, romandigit(n/10, "XLC")); strcat(buf, romandigit(n, "IVX")); return buf; } void main(void) { FILE *fin, *fout; int i, n; char *s; int count[256]; fin = fopen("preface.in", "r"); fout = fopen("preface.out", "w"); assert(fin != NULL && fout != NULL); fscanf(fin, "%d", &n); for(s="IVXLCDM"; *s; s++)//字符指针,指向字符串首地址,比较巧妙 count[*s] = 0; //将字符的ascii值为索引的位置初始化为0 for(i=1; i<=n; i++) for(s=roman(i); *s; s++) count[*s]++; for(s="IVXLCDM"; *s; s++) if(count[*s]) fprintf(fout, "%c %d\n", *s, count[*s]); exit(0); }
由于自身是初学者,编程能力有限,未达到专业程序员的水平,可能误导大家,请大家甄读;文字编辑也一般,文中可能会有措辞不当。博文中的错误和不足敬请读者批评指正。