2021寒假ACM集訓隊第一次訓練-搜索(一)


A-小宋銀行的貸款漏洞



PZ's solution

1.貸款利率\(\frac{a_1}{t}\)是不變的,例如\(x=100\quad t=2\),則貸款利率為\(\frac{1}{2}\),最終連本帶息須還的金額為\(100*(1+\frac{1}{2})^2=225\),所以得到\(ans=x*(1+\frac{a_1}{t})^t\)

2.對於\(t=ZERO\)的情況,會發現沒有貸款利率,直接得到\(ans=x\)

3.對於\(t=+INF\)的情況,在《高等數學上冊》,有一重要極限:

\[\lim_{x \to \infty} (1+\frac{1}{x})^x=e \]

對於本題,當\(t\)趨近於無限時,可以發現最終答案為

\[ans=x*\lim_{t \to \infty}(1+\frac{a_1}{t})^t \]

應用重要極限,得到:

\[\lim_{t \to \infty}(1+\frac{a_1}{t})^t = \lim_{t \to \infty}(1+\frac{a_1}{t})^{\frac{t}{a_1}a_1}=e^{a_1} \]

則可以得到\(ans=x*e^{a_1}\)

ps.本題對\(e\)的精度有要求,代碼中為最低精度,且答案向下取整;

  • TAG:數學



PZ.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
#define e (double)2.71828182845904 
string x,t;
long long T,res,a1;
double ans,new_ans;
int main(){
	scanf("%lld",&T);
	for(int j=1;j<=T;++j){
		res=ans=0;
		cin>>x>>t;
		a1=x[0]-'0';
		for(int i=0;i<x.size();++i)
			ans=ans*10+x[i]-'0';
		new_ans=1;
		if('0'<=t[0]&&t[0]<='9'){
			for(int i=0;i<t.size();++i)
				res=res*10+t[i]-'0';
			for(int i=1;i<=res;++i)
				new_ans*=(1+a1*1.0/res*1.0);
			ans=ans*new_ans;
			cout<<"#Case "<<j<<" : "<<(long long)(ans)<<endl;
		} else if(t=="ZERO"){
			cout<<"#Case "<<j<<" : "<<x<<endl;
		} else if(t=="+INF"){
			for(long long i=1;i<=a1;++i)
				new_ans*=e;
			ans=ans*new_ans;
			cout<<"#Case "<<j<<" : "<<(long long)(ans)<<endl;
		}
	}
	return 0;
}






B-三明治



Solution

思路借鑒於skylee的[JOISC2016]サンドイッチ

1.通過觀察,可以發現,只有如圖的四角的三明治才有機會再開始時被吃掉;


2.且經過簡單分析,可以得出,當一個大三明治中的一個小三明治被吃時,另一個也同時被吃掉一定使答案最優;
3.但通過思考發現,如果從四角開始搜索,嘗試吃三明治,會發現兩個問題:
1).如何防止不會吃到原先吃過的三明治;
2).如何防止不會因為走被吃過三明治的道路導致現在答案過小,或是沒有按照最優道路吃三明治而導致當前答案過大;


4.可以發現,從四角搜索具有局限性,所以轉換思維,考慮從每個大三明治向四周去吃(時間限制為5s,可以提醒時間復雜度可以寬松考慮);
5.觀察樣例和進行分析,可以發現,三明治一定會先一直往一個方向吃,直到不能吃當前方向的三明治才開始考慮換方向吃;
抽象來說,即為吃三明治的路徑具有某種單調性,且因為枚舉每個被吃的位置,所以可以實現得出最優解;

1).如當前三明治切法為\(N\),那么如果其為從左側吃到當前三明治,則其來的方向只能為左和下;
2).如當前三明治切法為\(N\),那么如果其為從右側吃到當前三明治,則其來的方向只能為右和上;
3).如當前三明治切法為\(Z\),那么如果其為從左側吃到當前三明治,則其來的方向只能為左和上;
4).如當前三明治切法為\(Z\),那么如果其為從右側吃到當前三明治,則其來的方向只能為右和上;

5).通過這種性質,可以使當前吃法借助前面的吃法,直接從前面的吃法鋪路過來吃,進行答案的累加;


6.具體方法上,可以枚舉每個大三明治位置,且因為有四角作為起始和單調性質的緣故,需要正序和倒序遍歷兩遍;
7.在搜索時,我們強制判斷能吃其相應來的地方,判斷其是否能吃到合法邊界;

1).如果相鄰的位置被吃掉,則相鄰位置對應的來的方向的邊界上的三明治也要被吃;
2).此時,如果吃不到邊界,或者出現吃成閉環(需要吃已經被吃的三明治)時,說明這個位置永遠都不能被吃到;

8.如圖代表代碼中的當前大三角形方向,具體細節見代碼;

  • TAG:搜索;剪枝



std.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<climits>
using namespace std;
#define N 405
char mp[N][N];
int n,m,res,flag,ans[N][N],vis[N][N];
int fx[]={0,-1,0,1};
int fy[]={-1,0,1,0};
bool check(int x,int y){ return 1<=x&&x<=n&&1<=y&&y<=m; }
void dfs(int x,int y,int d){
	if(vis[x][y]==-1){ flag=1; return; }
	//當vis[x][y]==-1時,說明當前三明治被吃過,且是必須要再被吃才能往后吃的
	//這樣的吃法當然是不合法的,使用flag作為標志,來表示當前三明治怎樣都不能被吃到
	if(vis[x][y]==1) return;
	//當vis[x][y]==1時,說明當前三明治被吃過,但原來的三明治是不必須依靠吃它才能往后吃,這只是一條原先就可以被擴散到的吃法
	res+=2;//一次直接將兩個小三明治全吃掉
	vis[x][y]=-1;
	//在開始吃必須吃的三明治前,給vis[x][y]置-1
	int p=((mp[x][y]=='N') ? 3 : 1);
	if(check(x-fx[d],y-fy[d])) dfs(x-fx[d],y-fy[d],d);
	if(check(x-fx[d^p],y-fy[d^p])) dfs(x-fx[d^p],y-fy[d^p],d^p);
	//正序時,如當前大三明治切法為N,則
		//要吃左和下,當前對應p為3
		//d^p=2^3=1
	//正序時,如當前大三明治切法為Z,則
		//要吃左和上,當前對應p為1
		//d^p=2^1=3
	//倒序時,如當前大三明治切法為N,則
		//要吃右和上,當前對應p為3
		//d^p=0^3=3
	//倒序時,如當前大三明治切法為Z,則
		//要吃右和下,當前對應p為1
		//d^p=0^1=1
	vis[x][y]=1;
	//吃完必須吃的三明治后,說明這是一條合法的吃法路線
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			cin>>mp[i][j];
	for(int i=1;i<=n;++i){
		flag=res=0;
	//關於單調性,可以通過res=0和循環中res不置零看出,具體來說,就是
        //如果flag沒有置1,說明前面的大三明治都能被吃掉,且有一個res值代表吃掉前面大三明治的一種合法結果
        //那么,當前的大三明治一但搜尋到vis[x][y]==1的位置,就說明可以接到前面大三明治的一種吃法,這樣就不需要再搜索,累加res的值了
        //且當前res值原本就是吃掉前面大三明治的結果加上當前大三明治吃到前面大三明治的結果的和
        memset(vis,0,sizeof(vis));
		for(int j=1;j<=m;++j){
			if(!flag) dfs(i,j,2);
			ans[i][j]=flag?INT_MAX:res;
		}
		flag=res=0;
		memset(vis,0,sizeof(vis));
		//再倒序一邊,因為來的方向不一樣,可能導致結果不同
		for(int j=m;j>=1;--j){
			if(!flag) dfs(i,j,0);
			ans[i][j]=min(ans[i][j],flag?INT_MAX:res);
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j)
			cout<<(ans[i][j]==INT_MAX ? -1 : ans[i][j])<<" ";
		cout<<endl;
	}
	return 0;
}






C-N皇后問題



Solution

題解見洛谷題單 【算法1-7】搜索 P1219 [USACO1.5]八皇后 Checker Challenge

  • TAG:簽到題



PZ.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,tot,ans[14],lie[14],zhu[25],ci[25];
void dfs(int x){
	if(x>n){
		++tot;
		return;
	} else {
		for(int i=1;i<=n;++i)
			if(lie[i]==0 && ci[i+x-1]==0 && zhu[n-1-i+x]==0){
				ans[x]=i;
				lie[i]=ci[i+x-1]=zhu[n-1-i+x]=1;
				dfs(x+1);
				lie[i]=ci[i+x-1]=zhu[n-1-i+x]=0;
			}
	}
}
int main(){
	scanf("%d",&n);
	dfs(1);
	printf("%d",tot);
	return 0;
}






D-生日蛋糕



Solution

ps.題解來自《信息學一本通·提高篇》

【思路點撥】
搜索框架:從下往上搜索,枚舉搜索面對的狀態有:正在搜索蛋糕第\(dep\)層,當前外表面面積\(s\),當前體積\(v\),第\(dep+1\)層的高度和半徑。不妨用數組\(h\)\(r\)分別記錄每層的高度和半徑。
整個蛋糕的“上表面”面積之和等於最底層的圓面積,可以在第\(M\)層直接累加到\(s\)中。這樣第\(M-1\)層往上的搜索中,只需要計算側面積。

剪枝

①上下界剪枝。
在第\(dep\)層時,只在下面的范圍內枚舉半徑和高度即可。
首先,枚舉\(R \in [dep,min \{ \sqrt{N-v},r[dep+1]-1\}]\)
其次,枚舉\(H \in [dep,min \{\frac{N-v}{R^2},h[dep+1]-1 \} ]\)
上面兩個區間右邊界中的式子可以通過圓柱體積公式\(\pi R^2H=\pi(N-v)\)得到。

對於\(R\),令\(H=1\),可以得到\(R=\sqrt{N-v}\)
對於\(H\),直接移項,可以得到\(H=\frac{N-v}{R^2}\)

②優化搜索順序。
在上面確定的范圍內,使用倒序枚舉。

③可行性剪枝。
可以預處理出從上往下前\(i(1\leqslant i\leqslant M)\)層的最小體積和側面積。顯然,當第\(1 \sim i\)層的半徑分別取\(1,2,3\cdots i\),高度也分別取\(1,2,3\cdots i\)時,有最小體積與側面積。
如果當前體積\(v\)加上\(1 \sim dep-1\)層的最小體積大於\(N\),則可以剪枝。

④最優性剪枝一。
如果當前表面積\(s\)加上\(1 \sim dep-1\)層的最小側面積大於已經搜到的結果,剪枝。

⑤最優性剪枝二。
利用\(h\)\(r\)數組,\(1 \sim dep-1\)層的體積可表示為

\[n-v=\sum_{k=1}^{dep-1}h[k]*r[k]^2 \]

\(1 \sim dep-1\)層的表面積可表示為

\[2\sum_{k=1}^{dep-1}h[k]*r[k] \]

因為

\[2\sum_{k=1}^{dep-1}h[k]*r[k]=\frac{2}{r[dep]}*\sum_{k=1}^{dep-1}h[k]*r[k]*r[dep] \geqslant \frac{2}{r[dep]}*\sum_{k=1}^{dep-1}h[k]*r[k]^2 \geqslant \frac{2(n-v)}{r[dep]} \]

1.\(2\sum_{k=1}^{dep-1}h[k]*r[k]\)表示的是上\(k\)層所有蛋糕的側面積;
2.當前層蛋糕的半徑\(r[dep]\)一定大於等於上層蛋糕的半徑\(r[k]\)
3.\(\sum_{k=1}^{dep-1}h[k]*r[k]^2\)表示的是上\(k\)層所有蛋糕的體積;
4.對於上\(k\)層蛋糕來說,它的體積一定會大於等於\((n-v)\)即當前所需的剩余蛋糕體積;

所以當\(\frac{2(n-v)}{r[dep]}+s\)大於已經搜到的結果時,可以剪枝。

加人以上五個剪枝后,搜索算法就可以快速求出該問題的最優解。

實際上,搜索算法面對的狀態可以看作一個多元組,其中每一元都是問題狀態空間中的一個“維度”。例如,本題中的層數\(dep\)、表面積\(S\)、體積\(V\)、第\(dep+1\)層的高度和半徑就構成狀態空間中的五個維度,其中每一個維度發生變化,都會移動狀態空間中的另一個“點”。這些維度通常在題目描述中也有所體現,它們一般在輸入變量、限制條件、待求解變量等非常關鍵的位置出現。讀者一定要注意提取這些“維度”,從而設計出合適的搜索框架。

搜索過程中的剪枝,其實就是針對每個“維度”與該維度的邊界條件,加以縮放、推導,得出一個相應的不等式,以減少搜索樹分支的擴張。例如,本題中的剪枝①、剪枝③和剪枝④,就是考慮與半徑、高度、體積、表面積這些維度的上下界進行比較而直接得到的。

為了進一步提高剪枝的效果,除了當前花費的“代價”之外,我們還可以對未來至少需要花費的代價進行預算,這樣更容易接近每個維度的上下界。例如,本題中求前\(dep-1\)層最小體積、最小側面積。剪枝⑤則通過表面積與體積之間的關系,對不等式進行縮放,\(\frac{2(n-v)}{r[dep]}\)這個式子也是對前\(dep-1\)層側面積的一個估計。這告訴我們在一般的剪枝不足以解決問題時,還可以結合各維度之間的聯系得到更加精准的剪枝。

  • TAG:數學;搜索;剪枝



PZ.cpp

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define inf 1e9+7
int n,m,minv[25],mins[25],ans=inf;
void dfs(int v,int s,int dep,int r,int h){
	if(dep==0){ if(v==n) ans=min(ans,s); return; }
	if(v+minv[dep-1]>n) return; //③可行性剪枝 
	if(s+mins[dep-1]>ans) return; //④最優性剪枝一 
	if(2*(n-v)/r+s>=ans) return; // ⑤最優性剪枝二 
	for(int nowh,i=r-1;i>=dep;--i){ //①上下界剪枝  ②優化搜索順序 
		if(dep==m) s=i*i;
		nowh=min((n-v-minv[dep-1])/(i*i),h-1);  
		for(int j=nowh;j>=dep;--j)
			dfs(v+i*i*j,s+2*i*j,dep-1,i,j);
	}
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=20;++i){ //預處理最小面積和體積
		minv[i]=minv[i-1]+i*i*i;
		mins[i]=mins[i-1]+2*i*i;
	}
	dfs(0,0,m,n+1,n+1); 
	if(ans==inf) puts("0");
	else printf("%d",ans);
	return 0;
}



E-小木棍



Solution

ps.題解來自《信息學一本通·提高篇》

從題意來看,要得到原始最短木棍的可能長度,可以按照分段數的長度,依此枚舉所有的可能長度\(len\);每次枚舉\(len\)時,用深度搜索判斷是否能用截斷后的木棍拼合出整數個\(len\),能用的話,找出最小的\(len\)即可。對於\(1S\)的時間限制,用不加任何剪枝的深度搜索時,時間效率為指數級,效率非常低,程序運行將嚴重超時。對於此題,可以從可行性和最優性上加以剪枝。

從最優性方面分析,可以做出以下兩種剪枝:

①設所有木棍的長度和是\(sum\),那么原長度(也就是需要輸出的長度)一定能夠被\(sum\)整除,不然就沒法拼了,即一定要拼出整數根。

②木棍原來的長度一定大於等於所有木棍中最長的那根。

綜合上述兩點,可以確定原木棍的長度\(len\)在最長木棍的長度與\(sum\)之間,且\(sum\)能被\(len\)整除。所以,在搜索原木棍的長度時,可以設定為從截斷后所有木棍中最長的長度開始,每次增加長度后,必須能整除\(sum\)。這樣可以有效地優化程序。

從可行性方面分析,可以再做以下七種剪枝:

①一根長木棍肯定比幾根短木棍拼成同樣長度地用處小,即短小地可以更靈活組合,所以可以對輸入地所有木棍按長度從大到小排序。

②在截斷后地排好序地木棍中,當用木棍\(i\)拼合原始木棍時,可以從第\(i+1\)后地木棍開始搜,因為根據優化①,\(i\)前面的木棍已經用過了。

③用當前最長長度的木棍開始搜,如果拼不出當前設定的原木棍長度\(len\),則直接返回,換一個原始木棍長度\(len\)

④相同長度的木棍不要搜索多次。用當前長度的木棍搜下去得不出結果時,用一支同樣長度的還是得不到結果,所以,可以提前返回。

⑤判斷搜到的幾根木棍組成的長度是否大於原始長度\(len\),如果大於,沒必要搜下去,可以提前返回。

⑥判斷當前剩下的木棍根數是否夠拼成木棍,如果不夠,肯定拼合不成功,直接返回。

⑦找到結果后,在能返回的地方馬上返回到上一層的遞歸處。

  • TAG:搜索;剪枝



PZ.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define inf 1e9+7
int a[51],n,m,tot,mina=inf,maxa;
void dfs(int rec,int sum,int now,int p,int q){
//rec為剩余需要拼出的木棍數量,sum為當前長度,now為預計長度,
//p為當前木棍長度,q為下一個木棍長度(使用離散數組代替排序)
	if(rec==0){ printf("%d",now); exit(0); }
	while(!a[q]) --q; p=min(p,q);
	if(sum==now){ dfs(rec-1,0,now,q,q); return; }
	for(int i=min(p,now-sum);i>=mina;--i)
		if(a[i]&&i+sum<=now){
			--a[i];
			dfs(rec,sum+i,now,i,q);
			++a[i];
			if(sum==0||sum+i==now) break;
		}
}
int main(){
	scanf("%d",&n);
	for(int x,i=1;i<=n;++i){
		scanf("%d",&x);
		if(x>50) continue;
		++a[x]; tot+=x;
		maxa=max(maxa,x);
		mina=min(mina,x);
	}
	for(int i=maxa;i<=tot>>1;++i) //因為一定木棍砍成的,最大長度即為tot/2
		if(tot%i==0)
			dfs(tot/i,0,i,maxa,maxa);
	printf("%d",tot);
	return 0;
}






std.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
bool cmp(int x,int y){ return x>y; }
int a[105],n,m,len,minlen,sum;
bool used[105],flag;
void dfs(int k,int last,int rest){
//k為第k根木棍,last為第k根上一節木棍編號,rest為第k根木棍還需要的長度
	if(k==m){ flag=1; return; }  
	int i;
	if(rest==0){
		for(i=1;i<=n;++i)  
			if(!used[i]){ used[i]=1; break; }
		dfs(k+1,i,len-a[i]); 
	}
	for(i=last+1;i<=n;++i) 
		if(!used[i]&&rest>=a[i]){
			used[i]=1;  
			dfs(k,i,rest-a[i]);
			used[i]=0;  
			int j=i;
			while(i<n&&a[i]==a[j]) ++i; 
			if(i==n) return;
		}
}

int main(){
	scanf("%d",&n); 
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		minlen<a[i]? minlen=a[i] :233; 
		sum+=a[i];  
	}
	sort(a+1,a+1+n,cmp); 
	for(int i=minlen;i<=sum;++i) 
		if(sum%i==0){  
			memset(used,0,sizeof(used));
			len=i;  
			used[1]=1;
			flag=0;
			m=sum/i; 
			dfs(1,1,len-a[1]);
			if(flag){ printf("%d",len); break; } 
		}
	return 0;
}






F-在地鐵和人海



PZ's Knowledge

ps.以下知識點摘自《信息學奧賽一本通·提高篇》

質數

一、定義

如果大於\(1\)的正整數\(p\)僅有正因子\(1\)\(p\),則稱\(p\)為質數(或者素數);大於\(1\)又不是質數的正整數稱為合數。

注意:\(1\)既不是質數,也不是合數;\(2\)是最小的質數,且為唯一的偶質數。質數的個數是無限的。

二、定理

1.算術基本定理

任何一個大於\(1\)的正整數都能唯一分解為有限個質數的乘積,可寫作:

\[N=P_1^{c_1}*P_2^{c_2}*\cdots *P_n^{c_n} \]

其中\(c_i\)都是正整數,\(P_i\)都是質數且滿足\(P_1<P_2<\cdots <P_n\)

2.質數分布定理

對正實數\(x\),定義\(\pi(x)\)為不大於\(x\)的質數個數,則有:\(\pi(x)\approx \frac{x}{lnx}\)

由質數定理可以給出第\(n\)個質數\(P(n)\)的漸近估計:\(P(n)\approx n*lnn\)

有結論:一個數\(N\)至多由一個大於\(\sqrt N\)的質因子

證明:假設\(a,b\)是由\(N\)分解出的兩個質因子,且\(a,b > \sqrt N\),那么\(a*b\)\(N\)的因子,然而\(a*b>N\),所以\(a*b\)不可能是\(N\)的因子,所以原命題成立。


約數

一、整除

整除與約數:設\(a,b\)是兩個整數,且\(b \ne 0\),如果存在整數\(c\),使\(a=b*c\),則稱\(a\)\(b\)整除,或\(b\)整除\(a\),記作\(b | a\)。此時,又稱\(a\)\(b\)的倍數,\(b\)\(a\)的因子。

\(a,b\)是兩個正整數,且\(b \ne 0\),則存在唯一的整數\(q\)\(r\),使

\[a=q*b+r\quad (0\leqslant r <|b|) \]

這個式子叫帶余除法,並記余數 \(r=a \;mod \;b\)。例如\(3 \;mod \;2=1\)

整除具有如下性質:

①若\(a|b\)\(a|c\),則\(\forall x,y\),有\(a|x*b+y*c\)
②若\(a|b\)\(b|c\),則\(a|c\)
③設\(m \ne 0\),則\(a|b\),當且僅當\(ma|mb\)
④若\(a|b\)\(b|a\),則\(a=\pm b\)

二、約數

算術基本定理的推論

在算數基本定理中,若正整數\(N\)被唯一分解為\(N=P_1^{c_1}*P_2^{c_2}*\cdots *P_n^{c_n}\),其中\(c_i\)都是正整數,\(P_i\)都是質數且滿足\(P_1<P_2<\cdots <P_n\),則\(N\)的正約數集合可寫作:

\[\{ P_1^{b_1}*P_2^{b_2}*\cdots*P_m^{b_m}\} \quad 其中 0 \leqslant b_i \leqslant c_i \]

\(N\)的正約數個數為:

\[(c_1+1)*(c_2+1)*\cdots*(c_n+1)=\prod_{i=1}^{n}(c_i+1) \]

\(N\)的所有正約數的和為:

\[(P_1^0+P_1^1+P_1^2+\cdots+P_1^{c_1})*\cdots*(P_n^0+P_n^1+P_n^2+\cdots+P_n^{c_n})=\prod_{i=1}^n(\sum_{j=0}^{c_i}(P_i)^j) \]

\(d \geqslant \sqrt N\)\(N\)的約數,則\(\frac{N}{d}\leqslant \sqrt N\)也是N的約數。換而言之,約數總是成對出現的(除了對於完全平方數,\(\sqrt N\)會單獨出現)。




PZ's solution

思路借鑒於杜宇一聲的BZOJ3629 聰明的燕姿 【例題精講】

1.題目要求很簡單,找到所有的正整數\(x\),要求\(x\)的正約數和為\(s\)

2.觀察所有正約數的和的式子,可以發現,\((P_n^0+P_n^1+P_n^2+\cdots+P_n^{c_n})\)是成塊出現的,我們可以使用深搜來尋找這些塊,讓\(s\)除掉它,然后尋找其他合法塊,直到\(s\)被除為\(1\),則深搜完畢;

3.設找到的某個合法塊大小為\(res\),剩余所有合法塊乘積為\(ans\),可以發現一種合法答案即為\(res*ans\)

4.特殊的,如果\(s-1\)為質數,則\(s-1\)的所有正約數和為\((s-1)^0+(s-1)^1=1+s-1=s\),就可以直接累加到答案中;

應用合法塊的概念,當\(s\)是被除過的情況時,一種合法答案即為\(res*(s-1)\),其實即為\(s-1\)是通過特判快速找到的合法塊;

  • TAG:數論;質數;約數;搜索



std.cpp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 50005 
//sqrt(2*10^9)約為44721
int p[N],s,ans[N];
bool vis[N];
//線性篩質數
void init(){
	vis[0]=vis[1]=1;
	for(int i=2;i<N;++i){
		if(!vis[i]) p[++p[0]]=i;
		for(int j=1;p[j]*i<N;++j){
			vis[p[j]*i]=1;
			if(i%p[j]==0) break;
		}
	}
}
//判斷質數,為了判斷s-1是否為質數而存在,s-1可能大於sqrt(2*10^9)
bool check(int x){
	if(x<N) return !vis[x];
	for(int i=1;p[i]*p[i]<=x&&i<=p[0];++i)
		if(x%p[i]==0) return 0;
	return 1;
}
void dfs(int tmp,int res,int s){
//tmp為當前篩到的質數數組的下標,res為合法塊累乘的結果,s為剩余需要拼湊的約數和
	if(s==1){ ans[++ans[0]]=res; return; }
    //找到一種所有合法塊都找到的情況,此時累乘得到的res即為答案
	if(s-1>=p[tmp]&&check(s-1)) ans[++ans[0]]=res*(s-1);
   	//對s-1情況的特判
        //Q:既然對s-1情況進行特判,不用擔心之后搜索合法塊,把s-1也搜索到造成答案重復嗎?
   	//A: (1)有可能出現 s-1 > sqrt(最初s) 的情況,所以應該有
        //   (2)因為確保p[i]*[i]<=s,所以之后的合法塊必然遍歷不到s-1這樣一個大質數,
        //      這是一種合法塊搜索過程中的一個盲區,所以才需要這個特判
	for(int i=tmp;p[i]*p[i]<=s;++i)
		for(int j=1+p[i],t=p[i];j<=s;t*=p[i],j+=t)
			if(s%j==0) dfs(i+1,res*t,s/j);
    //對於當前篩到的質數,遍歷其可能的合法塊
    //Q:為什么是j<=s?
    //A:因為可能出現j=s的情況,讓s%j==0
}
int main(){
	init();
	scanf("%d",&s);
	dfs(1,1,s);
	printf("%d\n",ans[0]);
	if(ans[0]){
		sort(ans+1,ans+1+ans[0]);
		for(int i=1;i<=ans[0];++i)
			printf("%d ",ans[i]);
	}
	return 0;
}



賽后感言

1.這次就A了A題和C題,其實不太應該,在B題上死磕了很多時間,因為錯誤的搜索方式的思維,導致浪費了許多時間;

2.就題目難度而言,B題、D題是不大好想的,F題是一個不錯的數論模板題,倒是B~F題都來自於《信息學奧賽一本通·提高篇》,倒是我沒想到的;

3.這次大家成績都比較慘淡,甚至為此調整了集訓方案,但藍橋杯近在眼前,需要抓緊努力了;

ps.回家忘記帶51開發板了,后面的內容看來暫時學不了了,不過stm32開發板馬上就到了,新的學習旅程要來了(~ ̄▽ ̄)~

ps.ps.OI賽制下,我居然B題能水33分,雖然我連樣例都過不了(⌐■_■),果然水分才是王道。




賽后練習題

1.HDU 1016 Prime Ring Problem

2.HDU 1241 Oil Deposits

3.HDU 2821 Pusher

4.HDU 3500 Fling

5.HDU 1010 Tempter of the Bone


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM