反素數 -- 數學


反素數就是區間內約數個數最多的那個數。

在ACM題目里,

一般是求約數最多而且數字最小的那個數,【1--n】

二是求約數剛好等於n的最小的那個數

三是求區間里的最小反素數【beign,end】

1和3有區別嗎?有,1可以加速,3只能暴力

先說下思路

思路 : 官方題解 : 

(1)此題最容易想到的是窮舉,但是肯定超時。

(2)我們可以知道,計算約數的個數和質因數分解有着很大的聯系: 若Q的質因數分解為:Q=p1^k1*p2^k2*…*pm^km(p1…pm為素數,k1…km≥1),則Q有(k1+1)(k2+1)…(km+1)個約數。但是質因數分解的時間復雜度很高,所以也會超時。

(3)通過以上的公式,我們可以“突發奇想”:為何不能把質因數分解的過程反過來呢? 這個算法就是枚舉每一個素數。初始時讓m=1,然后從最小的素數2開始枚舉,枚舉因子中包含0個2、1個2、2個2…k個2,直至m*2^k大於區間的上限N。在這個基礎上枚舉3、5、7……的情況,算出現在已經得到的m的約數個數,同時與原有的記錄進行比較和替換。直至所有的情況都被判定過了。 這個算法的優化:如果p1*p2*p3*……*pk>N(pi表示第i個素數),那么只要枚舉到p k-1,既不浪費時間,也不會遺漏。

(4)以上的算法還不是最好的,還可以繼續優化。 我們看以下的例子: 6=2*3 10=2*5 6和10的質因數分解“模式”完全相同,所以它們的約數個數是相同的。但是由於3<5,所以6<10。 12=2^2*3 18=3^2*2 12和18的質因數分解“模式”完全相同,所以它們的約數個數是相同的。但是由於12的質因數分解中2的指數大於3的指數,18的質因數分解中3的指數大於2的指數,所以12<18。 根據以上的舉例,我們也可以對(3)中的算法進行一個改進:可以在枚舉時進行一個優化,使得枚舉到的數字中2的指數不小於3的指數,3的指數不小於5的指數……這樣我們就能夠得到質因數分解“模式”相同的最小數(證明略)。再對於每一個得到的數進行比較和記錄。這個算法的優化力度極大,效率幾乎達到了極限。

 

每個思路都很好理解,所以

http://codeforces.com/problemset/problem/27/E

這是簽到題了,約數剛好等於n,那么最需dfs的時候判斷即可,用第4這個方法的思路,time 30ms

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;

#include <iostream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <string>
LL pr, mx, BEGIN, END = 1e18;
const int maxn=50+20;
int prime[maxn];//這個記得用int,他保存的是質數,可以不用開maxn那么大
bool check[maxn];
int total;
int n;
void initprime() {
    for (int i=2; i<=maxn-20; i++) {
        if (!check[i]) { //是質數了
            prime[++total]=i;//只能這樣記錄,因為后面要用
        }
        for (int j=1; j<=total; j++) { //質數或者合數都進行的
            if (i*prime[j]>maxn-20) break;
            check[i*prime[j]]=1;
            if (i%prime[j]==0) break;
            //關鍵,使得它只被最小的質數篩去。例如i等於6的時候。
            //當時的質數只有2,3,5。6和2結合篩去了12,就break了
            //18留下等9的時候,9*2=18篩去
        }
    }
    return ;
}
void dfs(LL curnum, int cnt, int depth, int up) {
    if (depth > total) return ; // 越界了,用不到那么多素數
    if ((cnt > mx || cnt == mx && pr > curnum) && cnt == n) {
        pr = curnum;
        mx = cnt;
    }
    for (int i = 1; i <= up; ++i) { //枚舉有多少個prime[depth]
        if (END / curnum < prime[depth]) return ;
        if ((BEGIN - 1) / curnum == END / curnum) return ; //區間不存在這個數的倍數
        curnum *= prime[depth]; //一路連乘上去
        dfs(curnum, cnt * (i + 1), depth + 1, i); // 2^2 * 3, 3最多2個
    }
}

void work() {
    cin >> n;
    dfs(1, 1, 1, 64);
    cout << pr << endl;
    return ;
}

int main() {
#ifdef local
    freopen("data.txt","r",stdin);
#endif
    initprime();
    work();
    return 0;
}
View Code

http://vjudge.net/problem/11177

求1--n里最小反素數,思路一樣

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;

#include <iostream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <string>
LL pr, mx, BEGIN, END;
const int maxn=50+20;
int prime[maxn];
bool check[maxn];
int total;
void initprime() {
    for (int i=2; i<=maxn-20; i++) {
        if (!check[i]) { 
            prime[++total]=i;
        }
        for (int j=1; j<=total; j++) { 
            if (i*prime[j]>maxn-20) break;
            check[i*prime[j]]=1;
            if (i%prime[j]==0) break;
        }
    }
    return ;
}

void dfs(LL curnum, int cnt, int depth, int up) {
    if (depth > total) return ; // 
    if ((cnt > mx || cnt == mx && pr > curnum) && curnum >= BEGIN) {
        pr = curnum;
        mx = cnt;
    }
    for (int i = 1; i <= up; ++i) { 
        if (END / curnum < prime[depth]) return ;
        if ((BEGIN - 1) / curnum == END / curnum) return ; //
        curnum *= prime[depth]; //
        dfs(curnum, cnt * (i + 1), depth + 1, i);
    }
}

void work() {
    pr = 0, mx = 0, BEGIN = 1;
    cin >> END;
    dfs(1, 1, 1, 64);
    cout << pr << " " << mx << endl;
    return ;
}

int main() {
#ifdef local
    freopen("data.txt","r",stdin);
#endif
    initprime();
    int t;
    cin >> t;
    while (t--) {
        work();
    }
    return 0;
}
View Code

難題是這個

http://acm.njupt.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1203

求解區間內[begin,end]最小反素數

用4思路啊,不行,為啥,因為4只關心約數個數,沒考慮過區間問題,很明顯2^2*3  4的思路限制了3的個數最多只能是2,

舉個例子[150,150]你怎么破?150 = 2*3*5*5,這種情況,在4中是不可能的,所以只能用3了

 

首先,數據范圍是n<=1e9,數據太大,如何快速算出來呢?我們注意到,如果是暴力算的話,最快的方法就是分解質因子,然后組合式計算啦。但是在算18和30的約數的時候,他們的

gcd(18,30)=6,其實是被重復算了的,那么我們思維反過來一下,把分解質因數變成用質因數去組合,使得變成區間內的數,這樣一來,我們在2*3的時候,*3就得到了18,*5就得到了30,能省掉一定的時間。但是還是會TLE。假如我們現在枚舉到的數是now,會不會它的約數根本就沒可能存在於區間里呢?也就是[begin,end]根本就沒這些約數。[7,11]內是不會存在6的倍數的。如果[1,begin-1]中6的約數和[1,end]中6的約數相同,說明什么?新加進去的區間[begin,end]根本就沒6的約數,這里可以剪枝。還是TLE!!可行性剪枝,如果一個數是now,現在枚舉一個新的質數去乘以它,去結合成新的數字,那么如果它無論組成什么其他數字,因子個數都不會超過當前最優值mx呢?怎么判斷呢?放縮咯,假如現在是2*3,重新去匹配一個新的素數5,那么,我就要看,當前2*3還能再乘多少個3呢?我記作q,那么這個新的匹配,最理想的情況下因子個數會多2­­q­倍,為什么呢?把那些3,全部替換成5*7*11*13這樣來算的話,就是有2q個了。別以為這樣沒用,當你搜[1,1e9]的時候,你枚舉到8000w,再去枚舉5那些是沒用的,根本就不可能,這里能剪很多。

其實我們還有一個根本的問題沒解決,那就是預處理素數到多大,還有萬一它是大素數呢?

想着預處理多少,要看數據,預處理出來的最大質數,primeMax‑2是要大過1e9才行的。為什么呢?因為你只有這樣,才能防止它數據是兩個大質數相乘的形式[primeMax2,primeMax2]。這里的因子個數是3,你枚舉不到這個primeMax的話,就只能得到2。

還有那個大素數,沒什么怕的,如果當前那個數now,幻想它乘以一個大質數,還是在end的范圍的話,就看看*2和mx誰大咯。乘以一個大素數也才加一倍因子數。其實乘以一個小的質因子的話,因子數會更多,這里主要是判斷只有一個大素數的特殊情況。枚舉不到那個大素數那里的。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;

#include <iostream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <string>
LL pr, mx, BEGIN, END = 1e18;
const int maxn=1e6+20;
int prime[maxn];//這個記得用int,他保存的是質數,可以不用開maxn那么大
bool check[maxn];
int total;
int n;
void initprime() {
    for (int i=2; i<=maxn-20; i++) {
        if (!check[i]) { //是質數了
            prime[++total]=i;//只能這樣記錄,因為后面要用
        }
        for (int j=1; j<=total; j++) { //質數或者合數都進行的
            if (i*prime[j]>maxn-20) break;
            check[i*prime[j]]=1;
            if (i%prime[j]==0) break;
            //關鍵,使得它只被最小的質數篩去。例如i等於6的時候。
            //當時的質數只有2,3,5。6和2結合篩去了12,就break了
            //18留下等9的時候,9*2=18篩去
        }
    }
    return ;
}
LL mypow (LL a, LL b) {
    LL ans = 1;
    while (b) {
        if (b & 1) {
            ans *= a;
        }
        a *= a;
        b >>= 1;
    }
    return ans;
}
void dfs (int cur,int cnt,LL now,LL from) {
    LL t=from*(cnt+1);//現在一共擁有的因子數
    if (now>=BEGIN && t>mx || t==mx && pr>now && now >= BEGIN) { //有得換了
        mx=t;
        pr=now;
    }
    for (int i=cur; i<=total; ++i) { //枚舉每一個素數
        LL temp = now*prime[i];
        if (END / now < prime[i]) return ; //這個數超出范圍了
        if (i == cur) { //沒有變,一直都是用這個數.2^k
            dfs(cur,cnt+1,temp,from);//唯一就是from沒變,一直都是用着2,不是新質數
        } else { //枚舉新質數了。
            LL k = (cnt+1)*from; //現在有K個因子
            LL q=(LL)(log(END/now) / log(prime[cur])); //2*3插入5時,用的是3來放縮
            LL add = k*mypow(2,q);
            if (add < mx) return ; //這里等於mx不return,可以輸出minpr
            if ((BEGIN-1)/now == END/now) return; //不存在now的倍數
            if (END/now > prime[total]) { //試着給他乘上一個大素數 [999991,999991]
                if ( k*2 > mx ) { //乘以一個大素數,因子數*2
                    pr = END;//如果只有一個大素數[1e9+7,le9+7]那么,就是端點值
                    //否則,是2*3*5*bigprime的話,結果不是最優的,
                    mx = k*2;
                }
            }
            dfs(i,1,temp,k);
        }
    }
    return;
}


void work() {
    cin >> BEGIN >> END;
    dfs(1,0,1,1);
    cout << mx << endl;
//    cout << pr << " " << mx << endl;
    return ;
}

int main() {
#ifdef local
    freopen("data.txt","r",stdin);
#endif
    initprime();
    work();
    return 0;
}
View Code

//cur:當前枚舉質數的下標,不用返回來枚舉了。

//cnt:分解質因式時:擁有(當前下標那個)素數多少個

//now:當前枚舉的那個數字,就是所有質因子相乘得到的數子

//from: 假如:2*2*3*5*7,然后枚舉3,記錄的是2*2,枚舉5,記錄的是2*2*3,

//如果是枚舉相同的數,則不用變,因為它記錄的是上一個不同的質因子一共擁有的因子數。

//所以乘上(cnt+1),就是包括上現在這個質因子一共擁有的因子數了。

dfs(1,0,1,1); //剛開始的時候,下標從1開始,擁有這個素數0個,當前數字最少也是1吧

 


免責聲明!

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



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