1. 程式人生 > 實用技巧 >NOIP2009普及組

NOIP2009普及組

T3】細胞分裂

【演算法】數論

【題解】均分的本質是A整除B,A整除B等價於A的質因數是B的子集

1.將m1分解質因數,即m1=p1^a1*p2^a2*...*pk^ak

所以M=m1^m2=p1^(a1*m2)*p2^(a2*m2)*...*pk^(ak*m2)

2.如果s[i](細胞初始個數)不能被M分解出來的質因數(即p1,p2...pn)中的某一個整除的話,這種細胞就永遠不可能裝入M個瓶子中

換句話說,如果s[i]分解出來的質因數不能包含M的所有質因數的話,就永遠不能整除M(即使乘方(分裂)後)。

當然並不需要真的把s[i]分解為質因數,只要有一個s[i]%pj(j=1..k)!=0就說明是-1。

3.確定不是-1後,需要計算最小分裂時間。

當s[i]^ans中包含的每個pi的個數(假設為bi)比M中包含的pi的個數(即ai*m2)多時,就能被M整除。

所以就是找到最小的ans,使每個bi>=(ai*m2)(i=1...n)。

這個ans=ceil(a[i]*m2/b[i]) (只適用於a[i]*m2比b[i]大時)(ceil表示向上取整)

在所有s[i]中尋找最小的ans就是答案。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cctype>
//https://www.cnblogs.com/onioncyc/p/5766587.html
//其實是一個數論的題目,質因數分解 
using namespace std;
const int maxm=30010,maxn=10010,inf=0x3f3f3f3f,eps=1e-6;
int pri[maxm],num[maxm],num2[maxm],s[maxn],tot=0,n,m,m2,ans;
int main(){
	scanf("%d %d %d",&n,&m,&m2);
	/*
	將m1分解質因數,即m1=p1^a1*p2^a2*...*pk^ak
所以M=m1^m2=p1^(a1*m2)*p2^(a2*m2)*...*pk^(ak*m2)
	*/
	for(int i=2;i*i<=m;i++){
		if(m%i==0){
			pri[++tot]=i;
			while(m%i==0){
				m/=i;
				num[tot]++;
			}
		}
	}
	if(m!=1) {
		pri[++tot]=m;num[tot]=1;
	}
	for(int i=1;i<=tot;i++) num[i]*=m2;
	ans=inf;
	/*
	如果s[i](細胞初始個數)不能被M分解出來的質因數(即p1,p2...pn)中的某一個整除的話,這種細胞就永遠不可能裝入M個瓶子中

換句話說,如果s[i]分解出來的質因數不能包含M的所有質因數的話,就永遠不能整除M(即使乘方(分裂)後)。

當然並不需要真的把s[i]分解為質因數,只要有一個s[i]%pj(j=1..k)!=0就說明是-1。
	*/
	for(int i=1;i<=n;i++){
		scanf("%d",&s[i]);
		bool f=0;
		memset(num2,0,sizeof(num2));
		for(int j=1;j<=tot;j++){
			if(s[i]%pri[j]!=0) f=1;
		}
		if(f) continue;
		/*
		3.確定不是-1後,需要計算最小分裂時間。
當s[i]^ans中包含的每個pi的個數(假設為bi)比M中包含的pi的個數(即ai*m2)多時,就能被M整除。
所以就是找到最小的ans,使每個bi>=(ai*m2)(i=1...n)。
這個ans=ceil(a[i]*m2/b[i]) (只適用於a[i]*m2比b[i]大時)(ceil表示向上取整)
在所有s[i]中尋找最小的ans就是答案。
		*/
		for(int j=1;j<=tot;j++){
			while(s[i]%pri[j]==0){
				num2[j]++;
				s[i]/=pri[j]; //s[i]中包含pi的個數 
			}
		}
		int maxs=0;
		for(int j=1;j<=tot;j++){
			if(num[j]>num2[j]){
				//num[j]是a[i]*m2  num2[j]是b[i] 
				maxs=max(maxs,(int)(ceil(1.0*num[j]/num2[j])+eps));
			}
		} 
		ans=min(ans,maxs);
	}
	if(ans==inf) ans=-1;
	printf("%d",ans);
	return 0;
}

  

T4】道路遊戲

聽起來比較複雜

但是看得出來資料範圍不太大,可以試一試暴力,用二維陣列存

其實有點像dp,用f[i]表示i這個時刻能夠得到的最大值

最外層列舉時間,第二次列舉結尾的地方,第三層列舉走過的時間,就自然得出了出發的地方,在出發的地方買就能得到一個比較的值

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
int cost[maxn];
int value[maxn][maxn]; //地點對應時間出現的價值 
int f[maxn]; //狀態壓縮至一維的,表示時間i能夠取得的最大值,列舉到達結尾和走過的距離k,然後找到最大值 
int n,m,p;
int main(){
	scanf("%d %d %d",&n,&m,&p);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) scanf("%d",&value[i][j]);
	}
	for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
	memset(f,0x9f,sizeof(f));
	f[0]=0;
	for(int i=1;i<=m;i++){ //時間 
		for(int j=1;j<=n;j++){ //結尾的地點 
			int summ=0; //直接增加列舉從j走到d的值,不用陣列存,也不用陣列計算,不然太麻煩 
			for(int k=1;k<=p&&k<=i;k++){
				int d=j-k;
				if(d<=0) d=d%n+n;
				summ+=value[d][i-k+1];
				f[i]=max(f[i],f[i-k]+summ-cost[d]); //從d買,然後走k 
			}
		}
	}
	printf("%d\n",f[m]);
return 0;
}