压位高精度
前言
本文的所有代碼用C++實現,若在代碼段里發現未定義的行為,請到最后的完整代碼里尋找該行為含義。若有錯誤敬請在評論區指正。謝謝您的閱讀。
引入
普通高精度每一位存一個數字,可是對于一個int型的存儲空間,可存的最大數值為2147483647,只存一個數字是極大的空間浪費。因此,為了在節約空間的同時減少時間復雜度,我們盡量在一個空間中多存幾位數字。對于乘法,兩數之積的位數最大可達到兩數的位數和,所以一個空間中存4位數字,可以保證不會溢出。
準備
一個數有以下幾個信息:長度、每一位數的數字、符號,因此我們定義Integer類的成員如下:
1 1 const int Max_Integer_Length=6000;//類外 2 2 private: 3 3 int digit[Max_Integer_Length],count; 4 4 short signum; 常量與成員操作
1.輸入輸出
類似于普通高精度,我們用一個char數組來輔助輸入。先將所有數據輸入到char數組當中,再把char數組每四位切割一次,倒序存到digit數組當中。(倒序是為了運算的時候方便處理)對于符號位,我們不妨使用一個start變量記錄是否為負,如果是,循環的時候對char數組則從第1位而非第0位開始。較麻煩的是對于char數組和digit數組位置對應公式的推導,因為一個是順序,另一個是逆序。
類似地,對于輸出,先判斷是否為負,如果是就輸出'-',然后對digit數組倒序輸出,注意要用零補齊4位。
1 inline Integer::Integer(const char *target) 2 { 3 memset(digit,0,sizeof(digit)); 4 int start=0,len=strlen(target); 5 if(target[0]=='-'&&(target[1]!='0'||len!=2)) 6 { 7 signum=-1; 8 ++start; 9 } 10 else if(target[0]!='0'||len!=1) 11 signum=1; 12 else 13 signum=0; 14 count=(len-start-1)/4; 15 for(int i=start,sum=0;i<len;++i) 16 { 17 sum=sum*10+target[i]-'0'; 18 if(!((len-i-1)%4)) 19 { 20 digit[count-(i-start)/4]=sum; 21 sum=0; 22 } 23 } 24 while(count>0&&!digit[count]) 25 --count; 26 } 27 inline std::istream& operator >>(std::istream &Cin,Integer &target) 28 { 29 scanf("%s",char_array_temp); 30 target=Integer(char_array_temp); 31 return Cin; 32 } 輸入 1 inline std::ostream& operator <<(std::ostream &Cout,const Integer &target) 2 { 3 if(target.signum==-1) 4 printf("-"); 5 printf("%d",target.digit[target.count]); 6 for(int i=target.count-1;i>-1;--i) 7 printf("%04d",target.digit[i]); 8 return Cout; 9 } 輸出2.大小比較
與普通高精度類似,先比符號,再比大小,最后逆序比較每一位。寫好'<'和'=='后,為了節省代碼量,可以用符號之間的關系來寫其它運算符。
1 inline bool operator <(const Integer &target1,const Integer &target2) 2 { 3 if(target1.signum!=target2.signum) 4 return target1.signum<target2.signum; 5 if(target1.signum==-1) 6 return target2.absolute_value()<target1.absolute_value(); 7 if(target1.count!=target2.count) 8 return target1.count<target2.count; 9 for(int i=target1.count;i>-1;--i) 10 if(target1.digit[i]!=target2.digit[i]) 11 return target1.digit[i]<target2.digit[i]; 12 return false; 13 } 14 inline bool operator >(const Integer &target1,const Integer &target2) 15 { 16 return !(target1<target2)&&!(target1==target2); 17 } 18 inline bool operator ==(const Integer &target1,const Integer &target2) 19 { 20 if(target1.signum!=target2.signum||target1.count!=target2.count) 21 return false; 22 for(int i=target1.count;i>-1;--i) 23 if(target1.digit[i]!=target2.digit[i]) 24 return false; 25 return true; 26 } 27 inline bool operator <=(const Integer &target1,const Integer &target2) 28 { 29 return target1<target2||target1==target2; 30 } 31 inline bool operator >=(const Integer &target1,const Integer &target2) 32 { 33 return !(target1<target2); 34 } 35 inline bool operator !=(const Integer &target1,const Integer &target2) 36 { 37 return !(target1==target2); 38 } 比較3.算術運算
對于加減乘法,和普通高精度相同,我們按照豎式計算的方式模擬。
加法從低位起,兩個運算數x,y的對應位分別相加,然后滿10000進1。時間復雜度為O(max(x.count,y.count))。
減法從低位起,被減數x的每一位分別減去減數y的對應位,如果為負則向下一位借1當10000。時間復雜度為O(max(x.count,y.count))。
乘法從低位起,第一個因數x的每一位與第二個因數y的每一位分別相乘,第i位與第j位所得的結果累加到結果的第i+j位,然后滿10000進1。時間復雜度為O(x.count*y.count)。
對于符號,加減法在異號時互相轉換調用,而乘法則只需要按同號為正,異號為負的方法得出符號。
注意減法和乘法可能產生的前導零需要刪除。
1 inline Integer Integer::absolute_value(void)const 2 { 3 if(!count&&!signum) 4 return *this; 5 else 6 { 7 Integer result=*this; 8 result.signum=1; 9 return result; 10 } 11 } 12 inline Integer Integer::opposite_number(void)const 13 { 14 Integer result=*this; 15 result.signum*=-1; 16 return result; 17 } 絕對值與相反數 1 inline Integer operator +(const Integer &target1,const Integer &target2) 2 { 3 if(target1.signum!=target2.signum) 4 if(!target1.signum||!target2.signum) 5 return target1.signum?target1:target2; 6 else return target1.signum<target2.signum?target2-target1.absolute_value():target1-target2.absolute_value(); 7 Integer result; 8 result.count=target1.count<target2.count?target2.count:target1.count; 9 result.signum=target1.signum; 10 for(int i=0;i<=result.count;++i) 11 { 12 result.digit[i]+=target1.digit[i]+target2.digit[i]; 13 result.digit[i+1]=result.digit[i]/10000; 14 result.digit[i]%=10000; 15 } 16 if(result.digit[result.count+1]) 17 ++result.count; 18 return result; 19 } 20 inline Integer operator -(const Integer &target1,const Integer &target2) 21 { 22 if(target1.signum!=target2.signum) 23 if(!target1.signum||!target2.signum) 24 return target1.signum?target1:target2.opposite_number(); 25 else return target1.signum<target2.signum?(target1.absolute_value()+target2).opposite_number():target1+target2.absolute_value(); 26 if(target1<target2) 27 return (target2-target1).opposite_number(); 28 Integer result; 29 if(target1==target2) 30 return result; 31 result.count=target1.count; 32 result.signum=1; 33 for(int i=0;i<=result.count;++i) 34 { 35 result.digit[i]+=target1.digit[i]-target2.digit[i]; 36 if(result.digit[i]<0) 37 { 38 --result.digit[i+1]; 39 result.digit[i]+=10000; 40 } 41 } 42 while(result.count>0&&!result.digit[result.count]) 43 --result.count; 44 return result; 45 } 46 inline Integer operator *(const Integer &target1,const Integer &target2) 47 { 48 Integer result; 49 if(!target1.signum&&!target2.signum) 50 return result; 51 result.signum=target1.signum*target2.signum; 52 result.count=target1.count+target2.count+1; 53 for(int i=0;i<=target1.count;++i) 54 for(int j=0;j<=target2.count;++j) 55 { 56 result.digit[i+j]+=target1.digit[i]*target2.digit[j]; 57 result.digit[i+j+1]+=result.digit[i+j]/10000; 58 result.digit[i+j]%=10000; 59 } 60 while(result.count>0&&!result.digit[result.count]) 61 --result.count; 62 return result; 63 } 加減乘對于除法,首先我們模擬豎式計算,首先將除數y的末位與被除數x的首位對齊(這個過程相當于擴大了除數),然后將除數的末位將不斷向被除數當前位的下一位移動(相當于將除數除以10000),直到到達其末位,并在每一次移動和對齊后在被除數上盡量多地在減去除數當前的相對大小,同時將減去的次數累加到結果的被除數當前位的對應位。
那么對于這里的盡量多地減去除數這一操作,我們寫普通高精度使用的是試除法,也就是不斷地判斷,如果被除數大于除數就減。
1 inline Integer operator /(Integer target1,Integer target2) 2 { 3 Integer result,temp; 4 if(!target1.signum||target1.absolute_value()<target2.absolute_value()) 5 return result; 6 if(!target2.signum) 7 throw std::logic_error("divide by zero!"); 8 result.signum=target1.signum*target2.signum; 9 result.count=target1.count-target2.count+1; 10 target1=target1.absolute_value(); 11 for(int i=result.count;i>-1;--i) 12 { 13 temp.zero(); 14 for(int j=0;j<=target2.count;++j) 15 temp.digit[i+j]=target2.digit[j]; 16 temp.count=target2.count+i; 17 temp.signum=1; 18 while(target1>=temp) 19 { 20 ++result.digit[i]; 21 target1-=temp; 22 } 23 } 24 while(result.count>0&&!result.digit[result.count]) 25 --result.count; 26 return result; 27 } 試除法可是我們計算一下這樣做的最壞時間復雜度:試除每次最多可能進行9999次,也就是每一位要執行9999次減法,總時間復雜度將達到9999*O(x.count*y.count),顯然對于數據稍微苛刻的題就會TLE。關鍵是而構造這樣的數據極為簡單,x的每一位都是9999,y為1,這就是最壞情況。未壓位之前,每一位最多試除9次,4位就是36次,遠遠低于9999次。
因此,我們不能直接試除。對于“盡量多”這一個字眼,我們可以聯想到一個時間復雜度較優的算法——二分答案。于是,我們對于每一次試除,在左邊界為0,右邊界為9999的區間內進行二分(邊界可以進行優化),找到盡量多的減法次數。由于這里的乘法的另一個因數為不超過9999的整數,存儲到Integer里只有一位,乘法復雜度僅為O(y.count),總時間復雜度為O(x.count*y.count)*k,其中k=log210000≈13。即使未壓位前使用了二分試除,4位的復雜度也有4*log210≈12,由于減法未壓位的常數更大,甚至壓位會更快。
但是,在洛谷 P2005 A/B Problem II中,這樣的二分還是會TLE。我們再對常數進行優化,在進行二分之前判斷此時的被除數和除數的大小,如果被除數更小就不再進行二分,這樣就避免了一次O(y.count)*k的二分。
1 inline Integer operator /(const Integer &target1,const Integer &target2) 2 { 3 Integer result,temp,now; 4 if(!target1.signum||target1.absolute_value()<target2.absolute_value()) 5 return result; 6 if(!target2.signum) 7 throw std::logic_error("divide by zero!"); 8 result.signum=target1.signum*target2.signum; 9 result.count=target1.count; 10 now.signum=1; 11 for(int i=result.count;i>-1;--i) 12 { 13 for(int j=now.count;j>-1;--j) 14 now.digit[j+1]=now.digit[j]; 15 now.digit[0]=target1.digit[i]; 16 if(now.digit[now.count+1]) 17 ++now.count; 18 now.signum=1; 19 if(now<target2) 20 continue; 21 int left=0,right=9999; 22 while(left<right) 23 { 24 int mid=(left+right)/2; 25 if(target2*Integer(mid)<=now) 26 left=mid+1; 27 else 28 right=mid; 29 } 30 result.digit[i]=left-1; 31 now-=Integer(left-1)*target2; 32 } 33 while(result.count>0&&!result.digit[result.count]) 34 --result.count; 35 return result; 36 } 二分除法取余可以直接通過除法和乘法來實現。對于被除數x和除數y,我們有x%y=x-x/y*y。
1 inline Integer operator %(const Integer &target1,const Integer &target2) 2 { 3 return target1-target1/target2*target2; 4 } 取余優點
相比起普通的高精度而言,壓位高精度將空間節約到了原來的四分之一,同時因為壓位位數減少,加減和比較操作時間也幾乎縮短到了原來的四分之一,乘法甚至幾乎縮短到了原來的十六分之一。
完整代碼
1 #ifndef _INTEGER_HPP_ 2 #define _INTEGER_HPP_ 3 4 #include<cstdio> 5 #include<cstring> 6 #include<iostream> 7 #include<stdexcept> 8 9 namespace PsephurusGladius 10 { 11 namespace integer 12 { 13 const int Max_Integer_Length=6000; 14 char char_array_temp[Max_Integer_Length]; 15 class Integer 16 { 17 private: 18 int digit[Max_Integer_Length],count; 19 short signum; 20 public: 21 Integer(void); 22 Integer(const Integer &target); 23 Integer(const long long &target); 24 Integer(const char *target); 25 operator char*(void); 26 void zero(void); 27 friend std::istream& operator >>(std::istream &Cin,Integer &target); 28 friend std::ostream& operator <<(std::ostream &Cout,const Integer &target); 29 Integer absolute_value(void)const; 30 Integer opposite_number(void)const; 31 Integer operator -(void)const; 32 friend bool operator <(const Integer &target1,const Integer &target2); 33 friend bool operator >(const Integer &target1,const Integer &target2); 34 friend bool operator <=(const Integer &target1,const Integer &target2); 35 friend bool operator >=(const Integer &target1,const Integer &target2); 36 friend bool operator ==(const Integer &target1,const Integer &target2); 37 friend bool operator !=(const Integer &target1,const Integer &target2); 38 friend Integer operator +(const Integer &target1,const Integer &target2); 39 friend Integer operator -(const Integer &target1,const Integer &target2); 40 friend Integer operator *(const Integer &target1,const Integer &target2); 41 friend Integer operator /(const Integer &target1,const Integer &target2); 42 friend Integer operator %(const Integer &target1,const Integer &target2); 43 Integer& operator ++(void); 44 Integer operator ++(int); 45 Integer& operator --(void); 46 Integer operator --(int); 47 Integer operator +=(const Integer &target); 48 Integer operator -=(const Integer &target); 49 Integer operator *=(const Integer &target); 50 Integer operator /=(const Integer &target); 51 Integer operator %=(const Integer &target); 52 }; 53 inline Integer::Integer(void):count(0),signum(0) 54 { 55 memset(digit,0,sizeof(digit)); 56 } 57 inline Integer::Integer(const Integer &target):count(target.count),signum(target.signum) 58 { 59 memcpy(digit,target.digit,sizeof(digit)); 60 } 61 inline Integer::Integer(const long long &target) 62 { 63 memset(digit,0,sizeof(digit)); 64 signum=target<0?-1:(target>0?1:0); 65 count=-1; 66 long long temp=target; 67 do 68 { 69 digit[++count]=temp%10000; 70 temp/=10000; 71 } 72 while(temp); 73 } 74 inline Integer::Integer(const char *target) 75 { 76 memset(digit,0,sizeof(digit)); 77 int start=0,len=strlen(target); 78 if(target[0]=='-'&&(target[1]!='0'||len!=2)) 79 { 80 signum=-1; 81 ++start; 82 } 83 else if(target[0]!='0'||len!=1) 84 signum=1; 85 else 86 signum=0; 87 count=(len-start-1)/4; 88 for(int i=start,sum=0;i<len;++i) 89 { 90 sum=sum*10+target[i]-'0'; 91 if(!((len-i-1)%4)) 92 { 93 digit[count-(i-start)/4]=sum; 94 sum=0; 95 } 96 } 97 while(count>0&&!digit[count]) 98 --count; 99 } 100 inline Integer::operator char*(void) 101 { 102 memset(char_array_temp,0,sizeof(char_array_temp)); 103 for(int i=count,len=0;i>-1;--i) 104 { 105 if(i==count) 106 { 107 len+=sprintf(char_array_temp+len,"%d",digit[i]); 108 continue; 109 } 110 len+=sprintf(char_array_temp+len,"%04d",digit[i]); 111 } 112 return char_array_temp; 113 } 114 inline void Integer::zero(void) 115 { 116 memset(digit,0,sizeof(digit)); 117 count=signum=0; 118 } 119 inline std::istream& operator >>(std::istream &Cin,Integer &target) 120 { 121 scanf("%s",char_array_temp); 122 target=Integer(char_array_temp); 123 return Cin; 124 } 125 inline std::ostream& operator <<(std::ostream &Cout,const Integer &target) 126 { 127 if(target.signum==-1) 128 printf("-"); 129 printf("%d",target.digit[target.count]); 130 for(int i=target.count-1;i>-1;--i) 131 printf("%04d",target.digit[i]); 132 return Cout; 133 } 134 inline Integer Integer::absolute_value(void)const 135 { 136 if(!count&&!signum) 137 return *this; 138 else 139 { 140 Integer result=*this; 141 result.signum=1; 142 return result; 143 } 144 } 145 inline Integer Integer::opposite_number(void)const 146 { 147 Integer result=*this; 148 result.signum*=-1; 149 return result; 150 } 151 Integer Integer::operator -(void)const 152 { 153 return opposite_number(); 154 } 155 inline bool operator <(const Integer &target1,const Integer &target2) 156 { 157 if(target1.signum!=target2.signum) 158 return target1.signum<target2.signum; 159 if(target1.signum==-1) 160 return target2.absolute_value()<target1.absolute_value(); 161 if(target1.count!=target2.count) 162 return target1.count<target2.count; 163 for(int i=target1.count;i>-1;--i) 164 if(target1.digit[i]!=target2.digit[i]) 165 return target1.digit[i]<target2.digit[i]; 166 return false; 167 } 168 inline bool operator >(const Integer &target1,const Integer &target2) 169 { 170 return !(target1<target2)&&!(target1==target2); 171 } 172 inline bool operator ==(const Integer &target1,const Integer &target2) 173 { 174 if(target1.signum!=target2.signum||target1.count!=target2.count) 175 return false; 176 for(int i=target1.count;i>-1;--i) 177 if(target1.digit[i]!=target2.digit[i]) 178 return false; 179 return true; 180 } 181 inline bool operator <=(const Integer &target1,const Integer &target2) 182 { 183 return target1<target2||target1==target2; 184 } 185 inline bool operator >=(const Integer &target1,const Integer &target2) 186 { 187 return !(target1<target2); 188 } 189 inline bool operator !=(const Integer &target1,const Integer &target2) 190 { 191 return !(target1==target2); 192 } 193 inline Integer operator +(const Integer &target1,const Integer &target2) 194 { 195 if(target1.signum!=target2.signum) 196 if(!target1.signum||!target2.signum) 197 return target1.signum?target1:target2; 198 else return target1.signum<target2.signum?target2-target1.absolute_value():target1-target2.absolute_value(); 199 Integer result; 200 result.count=target1.count<target2.count?target2.count:target1.count; 201 result.signum=target1.signum; 202 for(int i=0;i<=result.count;++i) 203 { 204 result.digit[i]+=target1.digit[i]+target2.digit[i]; 205 result.digit[i+1]=result.digit[i]/10000; 206 result.digit[i]%=10000; 207 } 208 if(result.digit[result.count+1]) 209 ++result.count; 210 return result; 211 } 212 inline Integer operator -(const Integer &target1,const Integer &target2) 213 { 214 if(target1.signum!=target2.signum) 215 if(!target1.signum||!target2.signum) 216 return target1.signum?target1:target2.opposite_number(); 217 else return target1.signum<target2.signum?(target1.absolute_value()+target2).opposite_number():target1+target2.absolute_value(); 218 if(target1<target2) 219 return (target2-target1).opposite_number(); 220 Integer result; 221 if(target1==target2) 222 return result; 223 result.count=target1.count; 224 result.signum=1; 225 for(int i=0;i<=result.count;++i) 226 { 227 result.digit[i]+=target1.digit[i]-target2.digit[i]; 228 if(result.digit[i]<0) 229 { 230 --result.digit[i+1]; 231 result.digit[i]+=10000; 232 } 233 } 234 while(result.count>0&&!result.digit[result.count]) 235 --result.count; 236 return result; 237 } 238 inline Integer operator *(const Integer &target1,const Integer &target2) 239 { 240 Integer result; 241 if(!target1.signum&&!target2.signum) 242 return result; 243 result.signum=target1.signum*target2.signum; 244 result.count=target1.count+target2.count+1; 245 for(int i=0;i<=target1.count;++i) 246 for(int j=0;j<=target2.count;++j) 247 { 248 result.digit[i+j]+=target1.digit[i]*target2.digit[j]; 249 result.digit[i+j+1]+=result.digit[i+j]/10000; 250 result.digit[i+j]%=10000; 251 } 252 while(result.count>0&&!result.digit[result.count]) 253 --result.count; 254 return result; 255 } 256 inline Integer operator /(const Integer &target1,const Integer &target2) 257 { 258 Integer result,temp,now; 259 if(!target1.signum||target1.absolute_value()<target2.absolute_value()) 260 return result; 261 if(!target2.signum) 262 throw std::logic_error("divide by zero!"); 263 result.signum=target1.signum*target2.signum; 264 result.count=target1.count; 265 now.signum=1; 266 for(int i=result.count;i>-1;--i) 267 { 268 for(int j=now.count;j>-1;--j) 269 now.digit[j+1]=now.digit[j]; 270 now.digit[0]=target1.digit[i]; 271 if(now.digit[now.count+1]) 272 ++now.count; 273 now.signum=1; 274 if(now<target2) 275 continue; 276 int left=0,right=9999; 277 while(left<right) 278 { 279 int mid=(left+right)/2; 280 if(target2*Integer(mid)<=now) 281 left=mid+1; 282 else 283 right=mid; 284 } 285 result.digit[i]=left-1; 286 now-=Integer(left-1)*target2; 287 } 288 while(result.count>0&&!result.digit[result.count]) 289 --result.count; 290 return result; 291 } 292 inline Integer operator %(const Integer &target1,const Integer &target2) 293 { 294 return target1-target1/target2*target2; 295 } 296 inline Integer& Integer::operator ++(void) 297 { 298 return *this=*this+Integer(1LL); 299 } 300 inline Integer Integer::operator ++(int) 301 { 302 Integer result=*this; 303 ++*this; 304 return result; 305 } 306 inline Integer& Integer::operator --(void) 307 { 308 return *this=*this-Integer(1LL); 309 } 310 inline Integer Integer::operator --(int) 311 { 312 Integer result=*this; 313 --*this; 314 return result; 315 } 316 inline Integer Integer::operator +=(const Integer &target) 317 { 318 return *this=*this+target; 319 } 320 inline Integer Integer::operator -=(const Integer &target) 321 { 322 return *this=*this-target; 323 } 324 inline Integer Integer::operator *=(const Integer &target) 325 { 326 return *this=*this*target; 327 } 328 inline Integer Integer::operator /=(const Integer &target) 329 { 330 return *this=*this/target; 331 } 332 inline Integer Integer::operator %=(const Integer &target) 333 { 334 return *this=*this%target; 335 } 336 } 337 } 338 339 #endif 壓位高精度模板轉載于:https://www.cnblogs.com/Psephurus-Gladius-zdx/p/10421919.html
總結
- 上一篇: (转)Python开发规范
- 下一篇: iScroll 5 API 中文版