玲珑杯1007-A 八进制大数加法(实现逻辑陷阱与题目套路)
题目连接:http://www.ifrog.cc/acm/problem/1056
Two octal number integers a, b are given, and you need calculate the result a - b in octal notation.
If the result is negative, you should use the negative sign instead of complement notation.
#include <iostream> #include <cstdio> #include <vector> #include <algorithm> #include <cstring> using namespace std; typedef long long ll; char x[10000],y[10000],c[10000]; int T; vector<int> ans; int jud(char *x,char *y){ int len_x=strlen(x); int len_y=strlen(y); if(len_x<len_y) return true; else if(len_x==len_y){ int i; for(i=0;i<len_x;++i){ if(y[i]>x[i]) return true; } } return false; } void Minus(){ int len_x=strlen(x); int len_y=strlen(y); int move=len_x-len_y; int i; //对齐 for(i=len_x-1;i>=len_x-len_y&&move;--i){ y[i]=y[i-move]; y[i-move]='0';//补零 } // printf("y:%s\nx:%s\n",y,x); // printf("lenx:%d\n",strlen(x)); // printf("leny:%d\n",strlen(y)); int cnt=0; for(i=len_x-1;i>=0;--i){ cnt++; if((x[i]-'0')<(y[i]-'0')&&cnt<=len_y){ x[i-1]-=1;//error i+1->i-1 x[i]+=8; x[i]-=y[i]-'0'; } else if(cnt<=len_y){ x[i]-=y[i]-'0'; } } for(i=len_x-1;i>=0;--i){ if(x[i]-'0'<0){ x[i-1]-=1; x[i]+=8; } ans.push_back(x[i]-'0'); } // for(i=ans.size()-1;i>=0;--i){ // printf("%d",ans[i]); // } // printf("\n"); // printf("lenx:%d\n",strlen(x)); } void shortx(){ int len=strlen(x); for(int i=0;i<len-1;++i){ x[i]=x[i+1]; } x[len-1]='\0'; } void shorty(){ int len=strlen(y); for(int i=0;i<len-1;++i){ y[i]=y[i+1]; } y[len-1]='\0'; } void add(){ int len_x=strlen(x); int len_y=strlen(y); int move=len_x-len_y; int i; //对齐 for(i=len_x-1;i>=len_x-len_y&&move;--i){ y[i]=y[i-move]; y[i-move]='0';//补零 } // printf("y:%s\nx:%s\n",y,x); // printf("lenx:%d\n",strlen(x)); // printf("leny:%d\n",strlen(y)); int cnt=0; for(i=len_x-1;i>=0;--i){ cnt++; x[i]+=y[i]-'0'; if(cnt==len_y) break; if(x[i]-'0'>=8){ x[i]-=8; x[i-1]+=1; } } int flag=0; for(i=len_x-1;i>=0;--i){ if(x[i]-'0'>=8){ x[i]-=8; if(i!=0) x[i-1]+=1; if(i==0) flag=1; } ans.push_back(x[i]-'0'); } if(flag) ans.push_back(1); // for(i=ans.size()-1;i>=0;--i){ // printf("%d",ans[i]); // } // printf("\n"); // printf("lenx:%d\n",strlen(x)); } int sign(char x){ if(x=='-') return -1; return 1; } void Swap(){ int len1=strlen(x); int len2=strlen(y); int j; for(j=0;j<len1;++j){ c[j]=x[j]; } c[j]='\0'; for(j=0;j<len2;++j){ x[j]=y[j]; } x[j]='\0'; for(j=0;j<len1;++j){ y[j]=c[j]; } y[j]='\0'; } void print(){ int len1=strlen(x); int len2=strlen(y); int len3=strlen(c); printf("len1%d len2%d len3%d\n",len1,len2,len3); for(int j=0;j<len1;++j){ printf("%c",x[j]); } printf("\n"); for(int j=0;j<len2;++j){ printf("%c",y[j]); } printf("\n"); for(int j=0;j<len3;++j){ printf("%c",c[j]); } printf("\n"); } void solvestr(char *x){ int len=strlen(x); // printf("slen:%d str:%s\n",len,x); int first=1,cnt=0,index=0,i; for(i=0;i<len;++i){ int t=x[i]-'0'; if(t==0){ cnt++; if(!first) c[index++]='0'+t; } else{ first=0; c[index++]='0'+t; } } if(cnt==len) c[index++]='0'+0; for(i=0;i<index;++i){ x[i]=c[i]; } x[index]='\0'; // printf("len:%d str:%s\n",len,x); } int main(){ scanf("%d",&T); while(T--){ ans.clear(); scanf("%s%s",x,y); solvestr(x);solvestr(y); int flag=0; if(jud(x,y)){ flag=1; Swap(); } Minus(); if(flag) printf("-"); int first=1;int cnt=0; for(int i=ans.size()-1;i>=0;--i){ int t=ans[i]; if(t==0){ cnt++; if(!first) printf("0"); } else{ first=0; printf("%d",t); } } if(cnt==ans.size()) printf("0"); printf("\n"); } return 0; } //下面这种2^96存不下 //void init(){ // base[0]=1; // for(int i=1;i<=33;++i){ // base[i]=8*base[i-1]; // } // } // ll toTen(char *x){ // int len=strlen(x); // int sum=0; // for(int i=0;i<len;++i){ // sum+=(x[i]-'0')*base[len-1-i]; // } // return sum; // }
如果我把它当成一个大数题的话。。那我就把它的输入当成字符串了,那么这就涉及到字符串的知识了
但是当时我考虑下一步就是,字符串相减,相减之前我发现,小数减去大数不太好实现。。其实是不太会
相比而言大数减去小数就容易多了,不够就向高位借,而且大数减去小数总能借到的。。(这里有坑1)
然后我想到了如何去判断输入的两个数串的大小,那么这个当然是一个经典的字典序问题
如果两个数串长度不相等(这里有坑2),那么长的肯定大,如果长度相等肯定是第一个y[i]>x[i]能判出y串较大(这里有坑3)
然后判断完了之后呢,如果y>x,我们就交换一下(这里有坑4),输出负号标志位置为1(这里有坑5)
然后我们开始做大数相减,我们发现虽然是大数减去小数,但是还是不能直接相减,你要把小数挪到和大数对齐(这里有坑6)
减完之后直接vector记录答案(这里有坑7,8)
最后在输出答案的时候我们把所有的前导零都去掉再输出(坑9)
然后我发现窝wa的妈妈都不认识了。。
然后就测试了相同位数的情况大于小于等于,然后是不同位数的大于等于小于,0,1,等特殊情况。。楞是没测出来
我也是服了。。可能是之前就根据样例改的。。所以也就能过个样例一样的数据,当时逻辑并不是很清楚只是有一组数据
满足了这个规律,然后我找到了编码的规律就搞上去了,这个方法是不可取的
窝突然想到既然我数据都没问题(其实只是没有测试到),那我肯定是少考虑了什么。。然后我回头看了一眼题目。。只扫了一眼
a-b(后面的题干都没看)。。然后我智障一般地想到。。万一人家输入的是负数怎么办。。那这样的话就有+,-交替四种情况,
然后我输入的时候做了特判。。然后还发现两个负数和一负一正的情况它们相减我是需要再写一个大数加法的。。
然后我又加了一个大数加法。。。还能想到进位比原来位数多的问题。。这里也判了
写了几组数据居然全是对的!我服!!
这tm是真不走运。。。然后最后找到一点数据一点一点改。。还是wa的妈妈都不认识。。
感到绝望啊。。啥时候才能A。。
然后我跑去看了题解。。根据题解我写了一个AC的程序。。大概长这样
#include <iostream> #include <cstdio> using namespace std; typedef long long ll; ll x,y; int T; int main(){ scanf("%d",&T); while(T--){ scanf("%o%o",&x,&y); if(y>x) printf("-%o\n",y-x); else printf("%o\n",x-y); } return 0; }
但是一开始还是wa了。。我的天呐!要崩溃了啊
因为我一开始x,y是int的,而题目给的数据范围是。。0~2^32-1。。。
窝们都知道int是32位,有一位作为符号位。。那么实际上int的范围在0~2^31-1啊!
那么他给的这个最大是int最大值的两倍。。超int了啊。。太狗了!
而且你%o读进来,如果直接相减会输出补码。。并不是题目要求的负号加绝对值
然后我们改成long long这个题就可以过了
那么下面我们来填一填上面的坑。。Orz
坑1:大数相减的实现。。减的时候因为我使用字符数组直接相减,那么还要有一个'0'在里面要处理。。
所以要减的话,要写成x[i]-=(y[i]-'0');
否则到时候输出的是乱码
并且我们在减的时候默认想到的是两个有效数字进行处理的,如果有前导零呢?
那么你这个东西就不对了。。你要强行把有效位数多和只是前导零多的但有效位数少的对齐。。这就不对了
实际上到这一步。。判断两个串大小的那个函数也是有锅的
坑2:通过字符串长度来判断两个数串的大小。。既然输入的是字符串那么我们就需要考虑前导零
如果一个数有效位数少但是前导零多。。我们这个逻辑可是要判断它是一个较大的数的啊!
坑3:我之前的那个想法是如果相等的话我不用管。。但是一旦第一位y[i]>x[i]就是y串大了
但是我的写法是。。如果相等的话我不管。。第一位是x[i]>y[i]也不管。。只要找到y[i]>x[i]就是y串大了
这显然不太对啊这样21 12,判断结果是12比较大。。错误的核心在于第一个不相等的串就可以出结果。。
不是y大就是x大。。,所以你必须首先找到第一个不相等的数位。。然后判断才是有效的啊。。
没有这个前提判断都是无效的。。所以我们在做一个判断的时候总要考虑它判断的前提。。即使是最基础的。。这是不太容易的
坑4:交换x和y数组,好像直接交换指针是不行的哦
反正指针各种出错(指针与数组类型。。中间还有一个坑。。就是他提示我有一个函数没有声明。。但是明明你写了对不对,
一般这种情况就是你和库函数有函数命名冲突,改一下名字就好了)。。然后我就又加了一个数组去交换。。
然后这里还是有坑。。你交换了之后还是要调用strlen函数是吧。。那么。。你交换之后后面的终止符'\0'就不好说了哦
这是很明显的写后读的问题。。所以我们在交换之后还要在末尾加上'\0',这样你在后面调用strlen才不会出错
对于这个还是养成习惯比较好。。另外。。数组开大一点。。才好放终止符号
坑5:我一开始设计的这个比较函数是如果x>y...返回true,然后我是!jud()就设置输出负号为真。。
那等于的时候也输出负号了。。所以不要想当然好吧。。,不合适就改一下咯,只有y>x我们才输出负号
坑6:先不说你这个对齐实现得对不对吧。。除了对齐。。我们还需要做另外一件事。。那就是补零
当然如果你是循环对齐的话,什么时候对齐。。什么时候补零要想清楚。。不要拿特判找规律
要找一组数据验证这个功能,然后到了54321 1这组数据的时候确实是出问题了
循环移位的话要移动多次。。如果你想一次移动到位。。既有不出现写后读的情况也有出现写后读的情况(写后读:原数据被修改之后又被读取原数据需求的语句读入)
所以我的一次移动到位是需要改成循环移位。。并且补零的
坑7:减完之后可不能直接记录答案啊大哥。。有的位被借成负数了。。我们还要最后处理这些负数的位啊
坑8:由于这个题是多组数据与CF不同,所以我们每次读入一组新的数据要考虑上一组读入的数据会不会对当前这组数据造成影响
所以我们在每次读入之前都要ans.clear()
坑9:把前导零去掉固然是没有错的。。但是如果我两个数相等最后结果全是零呢
你把零全部去掉可就啥都不显示了。。所以这里我们还要特判一下
尼看看。。看错题。。一道题卡了全场。。。真的是太可怕了啊!!
最后贴上我的八进制大数AC程序
#include <iostream> #include <cstdio> #include <vector> #include <algorithm> #include <cstring> using namespace std; typedef long long ll; const int maxn=33; char x[maxn],y[maxn],c[maxn]; int T; vector<int> ans; int jud(){ //判断哪个数串大 int len_x=strlen(x); int len_y=strlen(y); if(len_x<len_y) return true; else if(len_x==len_y){ int i; for(i=0;i<len_x;++i){ if(x[i]==y[i]) continue; if(x[i]-'0'>y[i]-'0') return false; else return true; // printf("x:%c y:%c\n",x[i],y[i]); } } return false; } void Minus(){ int len_x=strlen(x); int len_y=strlen(y); int move=len_x-len_y; int i; //对齐补零 int temp=move; while(temp--){ for(i=len_x-1;i>=1;--i){ y[i]=y[i-1]; } } for(i=0;i<move;++i){ y[i]='0'; } y[len_x]='\0'; // printf("x:%s\ny:%s\n",x,y); for(i=len_x-1;i>=0;--i){ if(x[i]<y[i]){ x[i]+=8; x[i-1]-=1; } x[i]-=y[i]; x[i]+='0'; } for(i=len_x-1;i>=0;--i){ if(x[i]-'0'<0){ x[i-1]-=1; x[i]+=8; } ans.push_back(x[i]-'0'); } } void Swap(){ //交换x和y数组通过c数组 int len1=strlen(x); int len2=strlen(y); int j; for(j=0;j<len1;++j){ c[j]=x[j]; } c[j]='\0'; for(j=0;j<len2;++j){ x[j]=y[j]; } x[j]='\0'; for(j=0;j<len1;++j){ y[j]=c[j]; } y[j]='\0'; } void solvestr(char *x){ //去掉前导0 int len=strlen(x); int first=1,cnt=0,index=0,i; for(i=0;i<len;++i){ int t=x[i]-'0'; if(t==0){ cnt++; if(!first) c[index++]='0'+t; } else{ first=0; c[index++]='0'+t; } } if(cnt==len) c[index++]='0'+0; for(i=0;i<index;++i){ x[i]=c[i]; } x[index]='\0'; } int main(){ scanf("%d",&T); while(T--){ ans.clear(); // memset(x,'\0',sizeof(x)); // memset(y,'\0',sizeof(y)); // memset(c,'\0',sizeof(c)); scanf("%s%s",x,y); // solvestr(x);solvestr(y); // printf("initial x:%s y:%s\n",x,y); int flag=0; if(jud()){ // printf("there!\n"); flag=1;//输出负号 Swap();//y>x 交换 } Minus(); if(flag) printf("-"); int first=1;int cnt=0; for(int i=ans.size()-1;i>=0;--i){ int t=ans[i]; if(t==0){ cnt++; if(!first) printf("0"); } else{ first=0; printf("%d",t); } } if(cnt==ans.size()) printf("0"); printf("\n"); } return 0; }
作为一次有多组输入的题目。。我们应该注意每次变量初始化和清空。。当然有些时候输入覆盖的话就不用管了
有些时候容器你一直不清空就会造成MLE,当然。。对于某些时限很紧的题。。你每次重新初始化。。很可能
在这里会造成超时的操作。。我们仍然要具体问题具体分析