hdu 4196 Remoteland(C++)(费马小定理求逆元,基本算术定理)

版权声明:本文为博主原创,未经博主允许不得转载 https://blog.csdn.net/Sherry_Yue/article/details/88597445

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 = P 1 a 1 P 2 a 2 P n a n N=P_1^{a_1}*P_2^{a_2}*…*P_n^{a_n} , 这里 P 1 &lt; P 2 &lt; . . . &lt; P n P_1&lt;P_2&lt;...&lt;P_n 是质数,其诸方幂 a i a_i 是正整数。

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

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

比如:令x=945,D=945*945=893025。
那么可以看出来: x = 945 = 3 3 5 1 7 1 x=945=3^3*5^1*7^1
根据基本算术定理,945的这个分解法是唯一的
所以 D = 945 945 = 3 6 5 2 7 2 D=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,有 a p a ( m o d   p ) a^p\equiv a(mod\space p) ;当a与p互质时,进一步有 a p 1 1 ( m o d   p ) a^{p-1}\equiv 1(mod\space p)

令p为题中的1,000,000,007。
考虑 c   m o d   p = ( a   m o d   p ) ( b   m o d   p ) c\space mod\space p=(a\space mod\space p)*(b\space mod\space p)
令A=a mod p, B=b mod p,C=c mod p;
则:
A = a   m o d   p = ( a 1 )   m o d   p = ( a   m o d   p ) ( 1   m o d   p ) \begin{aligned}A&amp;=a\space mod\space p\\ &amp;=(a*1)\space mod\space p\\ &amp;=(a\space mod\space p)*(1\space mod\space p)\end{aligned}

因为费马小定理,所以 ( b p 1 ) m o d   p = 1 (b^{p-1})mod\space p=1 ,即

A = ( a   m o d   p ) ( b p 1   m o d   p ) = ( a b p 1 )   m o d   p = [ ( a b )   m o d   p ] [ ( b p 2 )   m o d   p ] = ( c   m o d   p ) [ ( b   m o d   p ) p 2   m o d   p ] = C B p 2 \begin{aligned}A&amp;=(a\space mod\space p)*(b^{p-1}\space mod\space p) \\ &amp;=(a*b^{p-1})\space mod\space p\\ &amp;=[(a*b)\space mod\space p]*[(b^{p-2})\space mod\space p]\\ &amp;=(c\space mod\space p)*[(b\space mod\space p)^{p-2}\space mod\space p]\\ &amp;=C*B^{p-2}\end{aligned}

a   m o d   p = [ c   m o d   p ] [ b   m o d   p ] p 2 a\space mod\space p=[c\space mod\space p]*[b\space mod\space p]^{p-2}

D   m o d   p = [ ( n ! )   m o d   p ] [ ( )   m o d   p ] p 2 D\space mod\space p=[(n!)\space mod\space p]*[(奇数个因子的乘积)\space mod\space 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\n",ans);
}
int main(){
     LL n; init();
     //printf("%d\n",sz);
     while(scanf("%lld",&n)&&n){
          solve(n);
    }
    return 0;
}

另外的解题思路

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

题目中要求n!这给计算提供了便利,从1到n,依次判断是不是质数,如果为合数直接乘积。
容易知道,最后的乘积为 ( P 1 a 1 1 ) ( P 2 a 2 1 ) . . . . . . ( P n a n 1 ) (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\n", res);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Sherry_Yue/article/details/88597445
今日推荐