zoukankan      html  css  js  c++  java
  • 反素数 -- 数学

    反素数就是区间内约数个数最多的那个数。

    在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吧

  • 相关阅读:
    2013年3月1日星期五
    2013年2月26日星期二本地图片预览
    2013年3月2日星期六
    2013年第10周三低潮
    2013年第9周日见同学
    header发送Cookie
    HTTP Cookie header 中setcookie格式
    多台服务器共享session问题
    PHP中header头设置Cookie与内置setCookie的区别
    session原理及实现共享
  • 原文地址:https://www.cnblogs.com/liuweimingcprogram/p/5877411.html
Copyright © 2011-2022 走看看