zoukankan      html  css  js  c++  java
  • hdu 4196 Remoteland(C++)(费马小定理求逆元,基本算术定理)

    hdu 4196 Remoteland

    点击做题网站链接

    题目描述

    Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

    Problem Description
    In the Republic of Remoteland, the people celebrate their independence day every year. However, as it was a long long time ago, nobody can remember when it was exactly. The only thing people can remember is that today, the number of days elapsed since their independence (D) is a perfect square, and moreover it is the largest possible such number one can form as a product of distinct numbers less than or equal to n.
    As the years in Remoteland have 1,000,000,007 days, their citizens just need D modulo 1,000,000,007. Note that they are interested in the largest D, not in the largest D modulo 1,000,000,007.

    Input
    Every test case is described by a single line with an integer n, (1<=n<=10,000, 000). The input ends with a line containing 0.

    Output
    For each test case, output the number of days ago the Republic became independent, modulo 1,000,000,007, one per line.

    Sample Input
    4
    9348095
    6297540
    0

    Sample Output
    4
    177582252
    644064736

    中文翻译

    Problem Description
    在Remoteland共和国,人们每年都庆祝独立日。然而,因为独立日是很久以前的事了,没有人能记得它确切的时间。人们唯一能记住的是,今天自独立以来的天数(D)是一个完全平方数。而且,D的值等于小于等于n的部分数的乘积最大值。
    由于Remoteland国一年有1,000,000,007天,他们的公民只需要把D对1,000,000,007取模即可。注意,他们只对最大的D感兴趣,而不是最大的D模1,000,000,007感兴趣。

    Input
    每个测试用例都由一行整数n(1<=n<=10,000,000)表示。以输入“0"的行结尾。

    Output
    对于每个测试用例,输出自共和国独立以来的天数D(模1,000,000,007),每行一个。

    Sample Input
    4
    9348095
    6297540
    0

    Sample Output
    4
    177582252
    644064736

    解题思路

    题意:
    输入一个n,然后让你在1到n中选出某些数,把它们乘起来,使得乘积最大,并且乘积必须是完全平方数。

    分析:
    输入一个n,那么D的最大可能数就是n!n!(n的阶层)。

    根据基本算术定理:任何一个大于1的自然数N,都可以唯一分解成有限个质数的乘积 N=P1a1P2a2PnanN=P_1^{a_1}*P_2^{a_2}*…*P_n^{a_n} , 这里P1&lt;P2&lt;...&lt;PnP_1&lt;P_2&lt;...&lt;P_n是质数,其诸方幂aia_i 是正整数。

    所以,D=n(n1)(n2)1=n!D=n*(n-1)*(n-2)*…*1=n! 也可以拆成一堆素数的乘积。

    而且,从算数基本定理来考虑, 如果一个数字D是完全平方数,那么其所有质因子的指数都是偶数。因为,D=x2D=x^2,而x可以按照基本算术定理唯一拆成限个质数的乘积。既然是唯一,那么x2x^2,就是把x中有限个质数的指数乘以2,就可以得到D拆成有限个质数的形式。

    比如:令x=945,D=945*945=893025。
    那么可以看出来:x=945=335171x=945=3^3*5^1*7^1
    根据基本算术定理,945的这个分解法是唯一的
    所以 D=945945=365272D=945*945=3^6*5^2*7^2这个也是唯一的
    所以如果一个数字D是完全平方数,那么其所有质因子的指数都是偶数

    所以,我们只需要枚举 n!可以拆分的所有质因子,看其质因子的指数是否为偶数。

    如果当前处理的质因子的指数是偶数,那么起码说明这个质因子是符合要求的,暂且保留。

    但是如果是奇数,那么说明这个质因子不符合要求,所以我们就要删除这个当前处理的质因子。(删去当前的质因子不会影响结果。因为如果有当前的这个质因子,那么[1,n]之间一定有一个数字正好等于当前的这个质因子,所以我们除掉一个,就相当于将这个单独的数去掉,不会影响什么。)

    这样,保留符合要求的(即质因子的指数是偶数),删去不符合要求的(即质因子的指数是奇数),那么就相当于剔除了 n! 里面多余的质数。这样符合要求的质因子的乘积构成的数,既满足其是一个完全平方数,又满足其是小于等于n的不相同的部分数的乘积。

    则题目求解的步骤便是:
    1、打素数表。
    2、计算 n!中含有的每个素因子个数,如果为偶数就保留这个素因子的乘积,如果是偶数就删掉。
    3、把所有符合要求的都相乘得到D,然后每乘一次取模一次就不会爆。

    看似很简单,但当你实际写代码出来,发现其规模太大了,即使快速幂、快速乘也是会超时。

    只好另找思路:

    按照之前的思路:D为用 n!除去那些奇数个因子的乘积。等价于求a=c/b,其中a为D,c为 n! ,b为奇数个因子的乘积。由于是取模的,直接除必然不行。

    这里先贴出费马小定理:对任意a和任意质数p,有apa(mod p)a^pequiv a(modspace p);当a与p互质时,进一步有ap11(mod p)a^{p-1}equiv 1(modspace p)

    令p为题中的1,000,000,007。
    考虑c mod p=(a mod p)(b mod p)cspace modspace p=(aspace modspace p)*(bspace modspace p)
    令A=a mod p, B=b mod p,C=c mod p;
    则:
    A=a mod p=(a1) mod p=(a mod p)(1 mod p)egin{aligned}A&amp;=aspace modspace p\ &amp;=(a*1)space modspace p\ &amp;=(aspace modspace p)*(1space modspace p)end{aligned}

    因为费马小定理,所以(bp1)mod p=1(b^{p-1})modspace p=1,即

    A=(a mod p)(bp1 mod p)=(abp1) mod p=[(ab) mod p][(bp2) mod p]=(c mod p)[(b mod p)p2 mod p]=CBp2egin{aligned}A&amp;=(aspace modspace p)*(b^{p-1}space modspace p) \ &amp;=(a*b^{p-1})space modspace p\ &amp;=[(a*b)space modspace p]*[(b^{p-2})space modspace p]\ &amp;=(cspace modspace p)*[(bspace modspace p)^{p-2}space modspace p]\ &amp;=C*B^{p-2}end{aligned}

    a mod p=[c mod p][b mod p]p2aspace modspace p=[cspace modspace p]*[bspace modspace p]^{p-2}

    D mod p=[(n!) mod p][() mod p]p2Dspace modspace p=[(n!)space modspace p]*[(奇数个因子的乘积)space modspace p]^{p-2},p=1,000,000,007。

    利用这个,就可以求出D的结果。

    其实以上另找的思路就是费马小定理求逆元的推导过程。
    有关逆元的相关知识可以参考 逆元 这篇博客。

    则题目求解的步骤便是:
    1、打素数表
    2、计算 n! 中含有素因子个数,如果为奇数就保留这个素因子的乘积,以便最后相除
    3、求出除法结果

    问题解答

    #include<algorithm>
    #include<stdio.h>
    #define LL long long 
    using namespace std;
    
    const int MAXN = 1e7+11;
    const int MAXM = 1e7+11;
    const int mod =  1e9+7;
    const int inf = 0x3f3f3f3f;
    
    
    int prm[MAXN+2],sz; bool su[MAXN+2]; int fac[MAXM];  
    void init(){
        su[0]=su[1]=true;
        for(int i=2;i<=MAXN;i++){
            if(!su[i])  prm[++sz]=i;
            for(int j=1;j<=sz;j++){
                int t=i*prm[j];
                if(t>MAXN) break;
                su[t]=true;
                if(i%prm[j]==0) break;
            }
        }
        fac[0]=fac[1]=1; 
        for(int i=2;i<=MAXM;i++){
            fac[i]=(LL)fac[i-1]*i%mod;
        }
    }
    LL power(LL a,LL b,LL c){
        LL s=1,base=a%c;
        while(b){
            if(b&1) s=s*base%c;
            base=base*base%c;
            b>>=1;
        }
        return s;
    }
    LL inv(LL a){  // 费马小定理求逆元
        return power(a,mod-2,mod);
    }
    void solve(LL n){
        LL ans=fac[n]; LL temp=1;
        for(int i=1;i<=sz;i++){
            LL cnt=0; LL t=n;
            if(n/prm[i]){
                while(t){
                    cnt+=t/prm[i];
                    t/=prm[i];
                } 
                if(cnt&1) temp=temp*prm[i]%mod;  // 将所有要除的先都存起来,最后求一下逆元, 一开始这里直接就求逆元了,无限TLE。
            }else break; 
        }
        ans=ans*inv(temp)%mod; // 只要这里求一次逆元就行了 
        printf("%lld
    ",ans);
    }
    int main(){
         LL n; init();
         //printf("%d
    ",sz);
         while(scanf("%lld",&n)&&n){
              solve(n);
        }
        return 0;
    }
    

    另外的解题思路

    当第一次超时后,不用费马小定理的逆元,而找其他思路。

    题目中要求n!这给计算提供了便利,从1到n,依次判断是不是质数,如果为合数直接乘积。
    容易知道,最后的乘积为(P1a11)(P2a21)......(Pnan1)(P_1^{a1-1})*(P_2^{a2-1})......(P_n^{an-1})
    然后判断 n!的质因数为几次,若为偶次还需乘一次,若为奇次不须处理。

    即在这里采用一个巧妙的处理:在筛选素数时预处理阶乘,当这个数是素数先不用乘到阶乘中去,最后判断幂时如果是偶次幂再乘到结果中去。

    问题解答

    #include <stdio.h>
    
    long long ans[10000001];
    char comp[10000001];
    int primes[700000];
    
    int main()
    {
        /*预处理操作,筛素数和预处理阶乘*/
    	ans[0] = ans[1] = 1;//阶乘
    	int l = 0;
    	for(int i=2;i<=10000000;i++)
        {
    		ans[i] = ans[i-1];
    		if (!comp[i]) //如果是素数,就先不乘
    		{
    			primes[l++] = i;
    			if(i<4000)
    				for(int j=i*i;j<=10000000;j+=i)
    					comp[j] = 1;
    		}
    		else
    			ans[i] = (ans[i]*i)%1000000007;//是合数直接乘
    	}
    	
    	int n;
    	while(scanf("%d", &n) == 1 && n)
    	{
    		long long res = ans[n];
    		for (int i=0;i<l && primes[i]<=n/2;++i)
    		{
    			int cnt = 0;//cnt所求的是primes[i]的幂指数,这里用到的是勒让德定理
    			int tn = n;//tn统计素因子的幂次
    			do{
    				tn /= primes[i];
    				cnt += tn;
    			}while( tn>=primes[i] );
    			if( cnt%2==0 )//如果素数i的幂指数是偶数就乘到结果中去
    				res = (res*primes[i])%1000000007;
    		}
    		printf("%lld
    ", res);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Linux下的cut选取命令详解
    Linux下的hostname命令详解
    Linux下的sed流编辑器命令详解
    Linux下的设置静态IP命令详解
    模型评估方法
    模型验证方法
    超参数优化方法
    数据集划分方法
    数据预处理:标称型特征的编码和缺失值处理
    数据预处理:规范化(Normalize)和二值化(Binarize)
  • 原文地址:https://www.cnblogs.com/yuzilan/p/10626065.html
Copyright © 2011-2022 走看看