- 4.4上午:數學基礎
(qwq整成word和cpp了,它居然不能直接把文檔附上來)
- part 1:高精度運算
高精加和高精減就不說了,之前寫過博客了qwq,講一講高精乘和高精除吧。
1.高精度乘法(不知道為甚么害怕自己忘了老想再寫一遍):
題干(就很簡單惹):給定兩個數a,b,求他們的乘積(a和b都很大);
顯然如果直接乘,用一個數組存的話,可能會爆掉,(_int128也會爆的qwq),這時或許可以考慮將每一位分開存,然后就引入了神奇的高精度:
先讀入兩個數:這里用的是字符串char讀入然后把每一位數字存到數組中:
for(int i = lena-1;i >= 0;i--)a[lena-i] = a1[i]-48;//把字符串存進數組中 for(int i = lenb-1;i >= 0;i--)b[lenb-i] = b1[i]-48; /*剛剛搞了個大烏龍qwq(直接 a[lena-i] = a1[lena]-48;b[lenb-i] = b1[lenb]-48;*/ /*插入的主要代碼qwq,lena,lenb分別為字符串a1,b1的長度。這里從1~len分別存從個位到x位的數字*/
首先有一個必須要解決的問題:乘出來的數應該存到哪一位??不妨模擬一下乘法計算:
由此可以看出,第一個數的第i項*第二個數的第j項應該放在i+j-1的位置。
搞定了前面,就可以進行乘法運算惹.,真的吹爆lh的算法:lh是先乘起來再處理,趕腳這樣更好想也更好些一點;而且lh也沒有用什么特判if之類的,讓我這個蒟陣非常清楚自己在干什么qwq,比某ybt好多惹。
代碼惹:
for(int i = 1;i <= lena;i++) for(int j = 1;j <= lenb;j++) c[i+j-1] = c[i+j-1]+a[i]*b[j]; //進行乘法運算 int len=lena+lenb-1;//暫時定義最后答案的長度為lena+lenb-1(或許會更長,一會再處理) for(int i=1;i<=len;i++){ c[i+1]+=c[i]/10;//又搞了個烏龍導致答案算錯惹,這些大概都是我的弱點吧(寫一遍錯一遍qwq c[i]%=10; }
進行收尾的數據處理:
顯然乘法計算時可能會有超出lena+lenb-1的情況,所以我們或許大概(啊呸)(那得麻溜的處理啊)需要處理一下:
這里有一個小細節,處理前方非零數位時,要用while而不是if,因為前面的數可能有若干個(突然想不沒明白了qwq)
好惹,上代碼:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; char a1[100],b1[100]; int a[1000],b[1000],c[2000],lena,lenb,lenc; int main() { scanf("%s",a1); lena = strlen(a1); scanf("%s",b1); lenb = strlen(b1); for(int i = lena-1;i >= 0;i--)a[lena-i] = a1[i]-48;//把字符串存進數組中 for(int i = lenb-1;i >= 0;i--)b[lenb-i] = b1[i]-48; //剛剛搞了個大烏龍qwq(直接 a[lena-i] = a1[lena]-48;b[lenb-i] = b1[lenb]-48; for(int i = 1;i <= lena;i++) for(int j = 1;j <= lenb;j++){ c[i+j-1] += a[i]*b[j]; } //進行乘法運算 int len=lena+lenb-1;//暫時定義最后答案的長度為lena+lenb-1(或許會更長,一會再處理) for(int i=1;i<=len;i++){ c[i+1]+=c[i]/10; c[i]%=10; } while(c[len+1]!=0)len++; for(int i=len;i>=1;i--) printf("%d",c[i]); }//親測15*3是對的qwq
2.高精度除法(這里只講了高精/低精):
其實高精除在乘法的基礎上改一下核心語句就好惹,這里不細講惹,我gun去寫代碼了,希望我核心的語句沒有忘(寫崩了寫崩了!!!還是記不住qwq):
找到了錯誤,感覺自己還是在小細節上不夠仔細(buxianggenvsheng),像什么scanf沒有加引執符,%d搞成%s(這個是輸入字符數組b的時候忘記改過來了qwq)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; char a1[100]; int a[1000],b,c[2000],lena; int main() { scanf("%s",a1); lena = strlen(a1); scanf("%d",&b); for(int i = lena-1;i >= 0;i--)a[lena-i] = a1[i]-48;//把字符串存進數組中 for(int i = lena;i >=1;i--){//除法是唯一一個從高位開始算的 c[i]=a[i]/b; a[i-1]+=(a[i]%b)*10;//意思是把這一位/b余下的余數加到下一位(比這一位低一位的)去(加到下一位就*10了呀)
} while(c[lena]==0)lena--; for(int i=lena;i>=1;i--) printf("%d",c[i]); }//親測200/5有效
負數腫么辦???
加法:一個數是負數:變為減法 兩個數是負數:全部變為正數算加法,最后取負
減法:被減數是負數:全部變為正數算加法,最后取負 減數是負數:減數取負,變為加法 都是負數:都取負,變為減法,即(-減數)-(-被減數)
乘法:統計負數個數s 都變為非負數計算,若s為奇數,最后取負
- part2:模意義下的運算:
1.性質:
無除法運算,滿足基本的交換律、分配率、結合律 對中間結果取模不影響最終答案
eg:快速冪
計算a^b % p = ?
法一:分治思想:
這個題洛谷有板子(不過編譯失敗是個什么鬼??)(交了個板子題結果我炸了???):
行吧經過我無數次(5次欸qwq)的沒過板子emm我終於搞了個a了的代碼:
分治的思想就是一分為二嘛,求a^b%p,可以先求a^(b/2)%p再相乘,然后再%p,要注意的是b不整除2的時候最后還要乘一個a,然后存答案的話要用long long,要不然會爆的(我就爆了***)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int a,b,p; int fz(int a,int b,int p){ long long ans=1; if(b==0)return 1;//當b=0時,a^0=1; ans=fz(a,b/2,p);//搞分治,b每次/2 ans=ans*ans%p; if(b%2==1) ans=ans*a%p;//乘回來b為奇數的那個a return ans; } int main(){ scanf("%d%d%d",&a,&b,&p); cout<<a<<"^"<<b<<" mod "<<p<<"=";//這個是洛谷板子題的要求qwq cout<<fz(a,b,p)%p<<endl;//一定要模p啊qwq }
法二:快速冪(板子又沒過emm難到我數據又爆了??好像還真是qwq ):
行惹過了。突然想小反思一下:
感覺自己學信息奧賽這么久了,總是犯一些細節性的東西,基礎還是不扎實,還需要努力盤一盤基礎qwq
快速冪思想算是比較難理解代碼的思想了,其實數學思想還好,比較好理解,就怕代碼qwq
下面是某只zay的lh的解釋??
a^7 = a^1 * a^2 * a^4 2進制:111
a^11 = a^1 * a^2 * a^8 2進制:1011
a^25 = a^1 * a^8 * a^16 2進制:11001
所以首先計算出 a^1、 a^2、 a^4、 a^8 …,利用2進制表示出a,若第x位上為1,則乘a的x次方,若為0,則不乘。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; long long a,b,p; int kuaisumi(long long a,long long b,long long p){//鍾大佬kuaisumi(拼音)的操作天下無敵了 long long ans=1; while(b>0){ if(b&1)ans=ans*a%p;//滿足第x位上是1,ans乘上a^(2^(x-1)); a=a*a%p;//這里一直計算着a^2k次方2k=2^(x-1); b/=2; } return ans; } int main(){ scanf("%d%d%d",&a,&b,&p); cout<<a<<"^"<<b<<" mod "<<p<<"="; cout<<kuaisumi(a,b,p)%p<<endl; }
費馬小定理:
對於素數p和任意正整數a(0 ~ p-1),有a^(p-1) ≡ 1(mod p)
b * a = t
t * a^(p-2) = b t/a=b
//在模p意義下除以一個數等於乘這個數的p-2次方
應用:計算C(n,m) % (10^9+7) 10^9+7是質數 n ,m<=10,0000 Query 10,0000
思路:C(n, m) = n! / ( (n-m)! * m! )
= n! * ( (n-m)! * m! )^(p-2)
= n! * ( (n-m)! )^(p-2) * (m! )^(p-2) 預處理任意n!、(n! )^(p-2)
這個預處理怕不是要炸掉???(順便吐槽下lh的代碼質量qwq真的是金牌嗎??
日常寫標程寫炸qwq
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; long long n,m; long long b[110000],d[110000]; long long p=1e9+7; long long pow(long long a,long long b,long long p){//快速冪求階乘的p-2次方 long long ans=1; while(b>0){ if(b&1)ans=ans*a%p; a=a*a%p; b/=2; } return ans; } long long C(long long x,long long y){//求組合數的函數(思路見上 if(m>n)return -1; if(m==n||m==0)return 1; else return b[x]*d[x-y]%p*d[y]%p; } int main(){ scanf("%lld%lld",&n,&m); b[0]=1; for(int i=1;i<=100000;i++)b[i]=b[i-1]*i%p;//預處理1~100000的階乘 for(int i=1;i<=100000;i++)d[i]=pow(b[i],p-2,p)%p;//預處理1~100000階乘的p-2次方
cout<<C(n,m)<<endl; }
拓展:給定n,m,p求Cnm%p的值:
只需要改一下輸入就可以了,變為輸入三個數,p不再定義為1e9+7;
- part3:
1.最大公約數:
這里用到了輾轉相除(mo)法:請自行百度:
代碼qwq:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int a,b,x,y; int gcd(int a,int b){ if(b==0)return a; else return gcd(b,a%b); } int main(){ cin>>a>>b; cout<<gcd(a,b); }
最小公倍數:
這里有個小知識qwq:
lcm(a,b)*gcd(a,b)=a*b;
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int a,b,x,y; int gcd(int a,int b){ if(b==0)return a; else return gcd(b,a%b); } int main(){ cin>>a>>b; cout<<a/gcd(a,b)*b; }
end-