太強大了!分析令人震顫!
ACdtramers大佬此文章的傳送門
http://blog.csdn.net/ACdreamers/article/details/25049767
今天要我要講的是反素數,在ACM中也算是常見的考點,那麼對於搞ACM的同學來說,很有必要搞清楚它,所以接下來我會很詳細地講解。
在講解反素數之前,我們先來看反素數的概念。
反素數的定義:對於任何正整數,其約數個數記爲,例如,如果某個正整數滿足:對任意的正整數,都有,那麼稱爲反素數。
從反素數的定義中可以看出兩個性質:
(1)一個反素數的所有質因子必然是從2開始的連續若干個質數,因爲反素數是保證約數個數爲的這個數儘量小
(2)同樣的道理,如果,那麼必有t1>=t2>=t3>=….
在ACM競賽中,最常見的問題如下:
(1)給定一個數,求一個最小的正整數,使得的約數個數爲
(2)求出中約數個數最多的這個數
從上面的性質中可以看出,我們要求最小的,它的約數個數爲,那麼可以利用搜索來解。
以前我們求一個數的所有因子也是用搜索,比如,以每一個爲樹的一層建立搜索樹,深度爲以爲例進行說明,建樹如下:
以看出從根節點到每一個葉子結點這條路徑上的所有數字乘起來都是12的約數,所以12有6個約數。
搜索的思路就明顯了,從根節點開始進行深搜,到葉子結點,代碼如下:
void dfs(int dept,LL ans = 1)
{
if(dept == cnt)
{
fac[ct++] = ans;
return;
}
for(int i=0;i<=num[dept];i++)
{
dfs(dept+1,ans);
ans *= pri[dept];
}
}
回到我們的問題,同樣用搜索來求最小的。
題目:http://codeforces.com/problemset/problem/27/E
題意:給一個數,求一個最小的正整數,使得它的因子個數爲。
分析:與求因子的方法類似,先建立搜索樹,以每一個爲一層建立樹型結構,進行搜索,取最小的
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
typedef unsigned long long ULL;
const ULL INF = ~0ULL;
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
int n;
ULL ans;
void dfs(int dept,ULL tmp,int num)
{
if(num > n) return;
if(num == n && ans > tmp) ans = tmp;
for(int i=1;i<=63;i++)
{
if(ans / p[dept] < tmp) break;
dfs(dept+1,tmp *= p[dept],num*(i+1));
}
}
int main()
{
while(cin>>n)
{
ans = INF;
dfs(0,1,1);
cout<<ans<<endl;
}
return 0;
}
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1562
題意:求以內的因子最多的那個數。
分析:基本上跟上題差不多
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
typedef unsigned long long ULL;
const ULL INF = ~0ULL;
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
ULL ans,n;
int best;
void dfs(int dept,ULL tmp,int num)
{
//到葉子結點,返回
if(dept >= 16) return;
//num記錄的因子個數,如果遇到更小的,就更新
if(num > best)
{
best = num;
ans = tmp;
}
//當因子個數相同時,取值最小的
if(num == best && ans > tmp) ans = tmp;
for(int i=1;i<=63;i++)
{
if(n / p[dept] < tmp) break;
dfs(dept+1,tmp *= p[dept],num*(i+1));
}
}
int main()
{
while(cin>>n)
{
ans = INF;
best = 0;
dfs(0,1,1);
cout<<ans<<endl;
}
return 0;
}
題目:http://acm.timus.ru/problem.aspx?space=1&num=1748
分析:這道題主要注意數據處理。對於上面的兩題,數據範圍小,所以可以不用剪枝,本題就需要了
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
typedef unsigned long long ULL;
const ULL INF = ~0ULL;
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
ULL ans,n;
int best;
void dfs(int dept,int limit,ULL tmp,int num)
{
if(tmp > n) return;
if(num > best)
{
best = num;
ans = tmp;
}
if(num == best && ans > tmp) ans = tmp;
for(int i=1;i<=limit;i++)
{
double cur = (double)tmp;
if(n < cur*p[dept]) break;
dfs(dept+1,i,tmp *= p[dept],num*(i+1));
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n;
ans = INF;
best = 0;
dfs(0,60,1,1);
cout<<ans<<" "<<best<<endl;
}
return 0;
}
題目:http://acm.hdu.edu.cn/showproblem.php?pid=4542
題意:
給出一個數K和兩個操作
如果操作是0,就求出一個最小的正整數X,滿足X的約數個數爲K。
如果操作是1,就求出一個最小的X,滿足X的約數個數爲X-K。
分析:對於操作0,就是求反素數,直接搜索搞定。對於操作1,代表1至X中不是X的約數個數爲K。
代碼
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 50005;
typedef long long LL;
const LL INF = (((LL)1)<<62)+1;
int p[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
LL ans;
int n;
int d[N];
void Init()
{
for(int i=1;i<N;i++) d[i] = i;
for(int i=1;i<N;i++)
{
for(int j=i;j<N;j+=i) d[j]--;
if(!d[d[i]]) d[d[i]] = i;
d[i] = 0;
}
}
void dfs(int dept,int limit,LL tmp,int num)
{
if(num > n) return;
if(num == n && ans > tmp) ans = tmp;
for(int i=1;i<=limit;i++)
{
if(ans / p[dept] < tmp || num*(i+1) > n) break;
tmp *= p[dept];
if(n % (num*(i+1)) == 0)
dfs(dept+1,i,tmp,num*(i+1));
}
}
int main()
{
Init();
int T,tt=1;
scanf("%d",&T);
while(T--)
{
int type;
scanf("%d%d",&type,&n);
if(type) ans = d[n];
else
{
ans = INF;
dfs(0,62,1,1);
}
printf("Case %d: ",tt++);
if(ans == 0) puts("Illegal");
else if(ans >= INF) puts("INF");
else printf("%I64d\n",ans);
}
return 0;
}
還有黃學長博客
http://hzwer.com/3141.html
一個數約數個數=所有素因子的次數+1的乘積
舉個例子就是48 = 2 ^ 4 * 3 ^ 1,所以它有(4 + 1) * (1 + 1) = 10個約數
然後可以通過計算得一個2000000000以內的數字不會有超過12個素因子
並且小素因子多一定比大素因子多要優
預處理出前12個素數直接爆搜即可
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 0x7fffffff
#define ll long long
using namespace std;
int n,ans=1,num=1;
int p[15]={1,2,3,5,7,11,13,17,19,23,29,31};
void dfs(int k,ll now,int cnt,int last)
{
if(k==12)
{
if(now>ans&&cnt>num){ans=now;num=cnt;}
if(now<=ans&&cnt>=num){ans=now;num=cnt;}
return;
}
int t=1;
for(int i=0;i<=last;i++)
{
dfs(k+1,now*t,cnt*(i+1),i);
t*=p[k];
if(now*t>n)break;
}
}
int main()
{
scanf("%d",&n);
dfs(1,1,1,20);
printf("%d",ans);
return 0;
}
以及一名博客園大佬
http://www.cnblogs.com/tiankonguse/archive/2012/07/29/2613877.html
思維過程:
求[1..N]中最大的反素數–>求約數最多的數
如果求約數的個數 756=2^2*3^3*7^1
(2+1)(3+1)(1+1)=24
基於上述結論,給出算法:按照質因數大小遞增順序搜索每一個質因子,枚舉每一個質因子
爲了剪枝
性質一:一個反素數的質因子必然是從2開始連續的質數.
因爲最多只需要10個素數構造:2,3,5,7,11,13,17,19,23,29
性質二:p=2^t1*3^t2*5^t3*7^t4…..必然t1>=t2>=t3>=….
typedef __int64 INT;
INT bestNum; //約數最多的數
INT bestSum; //約數最多的數的約數個數
const int M=1000; //反素數的個數
INT n=500000;//求n以內的所有的反素數
INT rprim[M][2];
//2*3*5*7*11*13*17>n,所以只需考慮到17即可
INT prim[14]={2,3,5,7,11,13,17,19,23,29};
//當前走到num這個數,接着用第k個素數,num的約數個數爲sum,
//第k個素數的個數上限爲limit
void getNum(INT num,INT k,INT sum,INT limit) {
if(num>n)return;
if(sum>bestSum){
bestSum = sum;
bestNum = num;
}else if(sum == bestSum && num < bestNum){ //約數個數一樣時,取小數
bestNum = num;
}
if(k>=7) return; //只需考慮到素數17,即prim[6]
for(INT i=1,p=1;i<=limit;i++){ //素數k取i個
p*=prim[k];
getNum(num*p,k+1,sum*(i+1),i);
}
}
INT log2(INT n){ //求大於等於log2(n)的最小整數
INT i = 0;
INT p = 1;
while(p<n){
p*=2;
i++;
}
return i;
}
int getrprim(){//反素數的個數
int i = 0;
while(n>0){
bestNum = 1;
bestSum = 1;
getNum(1,0,1,log2(n));
n = bestNum - 1;
rprim[i][0]=bestNum;
rprim[i][1]=bestSum;
i++;
}
return i;
}