其他高效技巧与算法
目錄
- 打表
- 活用遞推
- 隨機(jī)選擇算法
打表
打表是一種典型的用空間換時間的技巧,一般指將所有可能需要用到的結(jié)果事先計(jì)算出來,
這樣后面需要用到時就可以直接查表獲得。
打表常見的用法有如下幾種:
- ①在程序中一次性計(jì)算出所有需要用到的結(jié)果,之后的查詢直接取這些結(jié)果。
這個是最常用到的用法,例如在一個需要查詢大量Fibonacci數(shù)F(n)的問題中,顯然每次從頭開始計(jì)算是非常耗時的,對Q次查詢會產(chǎn)生O(nQ)的時間復(fù)雜度;而如果進(jìn)行預(yù)處理,即把所有Fibonacci 數(shù)預(yù)先計(jì)算并存在數(shù)組中,那么每次查詢就只需要0(1)的時間復(fù)雜度,對Q次查詢就只需要O(n + Q)的時間復(fù)雜度(其中0(n)是預(yù)處理的時間)。 - ②在程序B中分一次或多次計(jì)算出所有需要用到的結(jié)果,手工把結(jié)果寫在程序A的數(shù)組中,然后在程序A中就可以直接使用這些結(jié)果。
這種用法般是當(dāng)程序的部分過程消耗的時間過多, 或是沒有想到好的算法,因此在另一個程序中使用暴力算法求出結(jié)果,這樣就能直接在原程序中使用這些結(jié)果。例如對n皇后問題來說,如果使用的算法不夠好,就容易超時,而可以在本地用程序計(jì)算出對所有n來說n皇后問題的方案數(shù),然后把算出的結(jié)果直接寫在數(shù)組中,就可以根據(jù)題目輸入的n來直接輸出結(jié)果。 - ③對一些感覺不會做的題目,先用暴力程序計(jì)算小范圍數(shù)據(jù)的結(jié)果,然后找規(guī)律,或許就能發(fā)現(xiàn)一些“蛛絲馬跡”。
這種用法在數(shù)據(jù)范圍非常大時容易用到,因?yàn)檫@樣的題目可能不是用直接能想到的算法來解決的,而需要尋找一些規(guī)律才 能得到結(jié)果。
活用遞推
有很多題目需要細(xì)心考慮過程中是否可能存在遞推關(guān)系,如果能找到這樣的遞推關(guān)系,就能使時間復(fù)雜度下降不少。
例如就一類涉及序列的題目來說,假如序列的每一位所需要計(jì)算的值都可以通過該位左右兩側(cè)的結(jié)果計(jì)算得到,
那么就可以考慮所謂的“左右兩側(cè)的結(jié)果”是否能通過遞推進(jìn)行預(yù)處理來得到,這樣在后面的使用中就可以不必反復(fù)求解。
思路:
直接暴力會超時。
換個角度思考問題,對一個確定位置的A來說,以它形成的PAT的個數(shù)等于它左邊P的個數(shù)乘以它右邊T的個數(shù)。
例如對字符串APPAPT的中間那個A來說,它左邊有兩個P,右邊有一個T,因此這個A能形成的PAT的個數(shù)就是2x 1=2。
于是問題就轉(zhuǎn)換為,對字符串中的每個A,計(jì)算它左邊P的個數(shù)與它右邊T的個數(shù)的乘積,然后把所有A的這個乘積相加就是答案。
那么有沒有比較快的獲得每一位左邊P的個數(shù)的方法呢?當(dāng)然有,只需要設(shè)定一個數(shù)組leftNumP,記錄每一位左邊P 的個數(shù)(含當(dāng)前位,下同)。
接著從左到右遍歷字符串,如果當(dāng)前位i是P,那么lefNumP[i] 就等于 lefNumP[i- 1] 加1;
如果當(dāng)前位i不是P,那么leftNumP[i]就等于leftNumP[i- 1]。于是只需要O(len)的時間復(fù)雜度就能統(tǒng)計(jì)出leftNumP 數(shù)組。
以同樣的方法可以計(jì)算出每一位右邊T 的個數(shù)。為了節(jié)省代碼量,不妨在統(tǒng)計(jì)每一位右邊T的個數(shù)的過程中直接計(jì)算答案ans。
具體做法是:定義一個變量rightNumT,記錄當(dāng)前累計(jì)右邊T的個數(shù)。
從右往左遍歷字符串,如果當(dāng)前位i是T,那么令rightNumT加1;否則,如果當(dāng)前位i是A,
那么令ans加上lefNumP[1]與rightNumT的乘積(注意取模)。這樣,當(dāng)遍歷完字符串時,就得到了答案ans。
注意點(diǎn)
①采用分別遍歷P、A、T的位置來統(tǒng)計(jì)的方法會超時。
②記得取模。
③本題與PAT B1045/A1101的思路很像,注意認(rèn)真體會這兩道題的思想。
書上的代碼:
#include<cstdio> #include<cstring> const int MAXN=100010; const int MOD=1000000007; char str[MAXN];//字符串 int leftNump[MAXN]={0};//每一位左邊含p的個數(shù) int main() {scanf("%s",str);int len=strlen(str);for(int i=0;i<len;i++)//從左到右遍歷字符串 {if(i>0)//如果不是0號位 {leftNump[i]=leftNump[i-1]; }if(str[i]=='P')//當(dāng)前為是P {leftNump[i]++;//加1 } }int ans=0,rightNumT=0;//ans為答案,rightNumT記錄右邊T的個數(shù)for(int i=len-1;i>=0;i--)//從右到左遍歷 {if(str[i]=='T'){rightNumT++;} else if(str[i]=='A'){ans=(ans+leftNump[i]*rightNumT)%MOD;}} printf("%d\n",ans);return 0; }自己寫的代碼:
#include<iostream> #include<cstdio> #include<string> #define INF 1000000007 #define max 100005 using namespace std; int P[max]; int T[max]; int main(void) {string str;int i=0;cin>>str;long long int sum=0;if(str[0]=='P')//初始化邊界 P[0]=1;elseP[0]=0;for(i=1;i<str.length();i++)//統(tǒng)計(jì)P各個位置的個數(shù) {if(str[i]=='P'){P[i]=P[i-1]+1;}else{P[i]=P[i-1];}}if(str[str.size()-1]=='T')//初始化邊界 T[str.size()-1]=1;elseT[str.size()-1]=0;for(i=str.size()-2;i>=0;i--)//統(tǒng)計(jì)T各個位置的個數(shù) {if(str[i]=='T'){T[i]=T[i+1]+1;}else{T[i]=T[i+1];}}for(i=1;i<str.size()-1;i++){if(str[i]=='A')sum+=P[i-1]*T[i+1];}printf("%d\n",sum%INF); }隨機(jī)選擇算法
總結(jié)
- 上一篇: 使用next_permutation()
- 下一篇: 一个判断字符是不是10进制数的函数---