1. 程式人生 > >HFOI2017.9.12 複習賽01題解

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;
}