HFOI2017.9.12 複習賽01題解
01:質數的和與積
- 描述
-
兩個質數的和是S,它們的積最大是多少?
- 輸入
- 一個不大於10000的正整數S,為兩個質數的和。
- 輸出
- 一個整數,為兩個質數的最大乘積。資料保證有解。
- 樣例輸入
-
50
- 樣例輸出
-
589
- 來源
-
《奧數典型題舉一反三(小學五年級)》 (ISBN 978-7-5445-2882-5) 第三章 第二講 例1
這是一道小學數學題啊。。。
我的方法是先用線性篩素數打出一個素數表,然後就i從S/2開始,往下減少,如果i和(S-i)都是質數,那麼輸出他們的積,並且return 0;
程式碼:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; bool tf[10010]={true,true};//素數表 int s; int main() { for(int i=2;i<=100;i++)//線性篩素數,對於i<=sqrt(maxn),將i的每個倍數標記為合數 { if(tf[i])continue;//如果i已經是合數,那麼他的倍數肯定已經被標記過了,所以無需再次標記 for(int j=2;i*j<=10000;j++)tf[i*j]=true;//如果是質數,進行標記操作 } scanf("%d",&s); for(int i=s/2;i>=2;i--)if(!tf[i]&&!tf[s-i])//如果i和(s-i)都是質數 { printf("%d\n",i*(s-i)); return 0;//輸出並返回 } }
02:大整數乘法
- 描述
-
求兩個不超過200位的非負整數的積。
- 輸入
- 有兩行,每行是一個不超過200位的非負整數,沒有多餘的前導0。
- 輸出
- 一行,即相乘後的結果。結果裡不能有多餘的前導0,即如果結果是342,那麼就不能輸出為0342。
- 樣例輸入
-
12345678900 98765432100
- 樣例輸出
-
1219326311126352690000
- 來源
- 程式設計實習2007
這題的資料有點水,不用FFT都可以過。。。但是我不會FFT
我寫的程式碼是偏向於模板型別的(中了一點面向物件的毒),用了過載運算子
高精度的中心思想是通過類似於筆算的方法。比如說我們要計算200位+200位的加法,我們是從最低位(個位)開始,一步一步往上加,同時儲存進位。
為了閱讀和編寫的方便,我沒有用高精壓位:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=410; struct bigint { int l,num[maxn];//l是大整數的長度,num儲存每一位,num[0]是個位,依此類推 bigint(){l=1;memset(num,0,sizeof num);}//初始化 void in(){char s[maxn];scanf("%s",s);l=strlen(s);for(int i=0;i<l;i++)num[i]=s[l-i-1]-'0';}//輸入函式 void out(){for(int i=l-1;i>=0;i--)printf("%d",num[i]);printf("\n");}//輸出函式,內建 bigint operator+(bigint a)//加法操作 { bigint ans; ans.l=max(l,a.l)+1;//更新長度 for(int i=0;i<ans.l;i++)ans.num[i]=num[i]+a.num[i];//計算每一位的值 for(int i=1;i<ans.l;i++)ans.num[i]+=ans.num[i-1]/10,ans.num[i-1]%=10;//計算進位 if(!ans.num[ans.l-1])ans.l--;//判斷最高位是否是0 return ans;//返回 } bigint move(int a)//移位操作,move(a)代表*(10^a) { bigint ans; ans.l=l+a; for(int i=0;i<l;i++)ans.num[i+a]=num[i]; return ans; } bigint operator*(int a)//乘法操作,乘以一個小於10的數 { bigint ans; ans.l=l+1; for(int i=0;i<ans.l;i++)ans.num[i]=num[i]*a;//計算 for(int i=1;i<ans.l;i++)ans.num[i]+=ans.num[i-1]/10,ans.num[i-1]%=10;//進位 if(!ans.num[ans.l-1])ans.l--;//判斷最高位是否為0 return ans;//返回 } bigint operator*(bigint a)//乘法,乘以一個大整數 { bigint ans; ans.l=l+a.l+1;//更新長度 for(int i=0;i<l;i++)ans=ans+(a*num[i]).move(i);//對於每一位,加上乘以這一位的值 while(ans.l!=1&&!ans.num[ans.l-1])ans.l--;//判斷是否需要更新總長度 return ans;//返回 } }a,b; int main() { a.in();b.in();//輸入 (a*b).out();//輸出 return 0; }
03:字元環
- 描述
-
有兩個由字元構成的環。請寫一個程式,計算這兩個字元環上最長連續公共字串的長度。例如,字串“ABCEFAGADEGKABUVKLM”的首尾連在一起,構成一個環;字串“MADJKLUVKL”的首尾連在一起,構成一個另一個環;“UVKLMA”是這兩個環的一個連續公共字串。
- 輸入
- 一行,包含兩個字串,分別對應一個字元環。這兩個字串之間用單個空格分開。字串長度不超過255,且不包含空格等空白符。
- 輸出
- 輸出一個整數,表示這兩個字元環上最長公共字串的長度。
- 樣例輸入
-
ABCEFAGADEGKABUVKLM MADJKLUVKL
- 樣例輸出
-
6
用DP方法求兩個字串的最長連續公共字串的長度的方法描述如下:定義f[i][j]表示以字串1的第i位和字串2的第j位為結尾的最長連續公共字串的長度,可以得出狀態轉移方程為:f[i][j]=(s1[i]==s2[j]?f[i-1][j-1]+1:0)
求環上的最長連續公共字串,使用倍增的思想,把兩個字串翻倍,例如AB翻倍為ABA,ABBCCCD翻倍為ABBCCCDABBCCC,然後再求最長連續公共字串的長度。
注意:現在得出的答案還不是正確的答案,因為這樣的做法可能會把一個元素使用兩次。可以證明,如果存在著一個長度為n+1的最長連續公共字串,那麼一定存在一個長度為n的最長連續公共字串,所以最後的答案就要輸出min(ans,min(l1,l2))
複雜度分析:時間複雜度O(NM),空間複雜度O(NM),通過滾動陣列法可以優化到O(min(N,M))(N,M是兩個字串的長度)
程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=610;
char s1[maxn],s2[maxn];
int l1,l2,L1,L2,f[maxn][maxn],ans=0;
int main()
{
memset(f,0,sizeof f);
scanf("%s%s",s1,s2);
l1=strlen(s1);l2=strlen(s2);
for(int i=0;i<l1-1;i++)s1[i+l1]=s1[i];
for(int i=0;i<l2-1;i++)s2[i+l2]=s2[i];
L1=l1*2-1;L2=l2*2-1;
for(int i=0;i<L1;i++)for(int j=0;j<L2;j++)
if(s1[i]==s2[j])f[i+1][j+1]=f[i][j]+1,ans=max(ans,f[i+1][j+1]);
printf("%d\n",min(ans,min(l1,l2)));
return 0;
}