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的阶层)。
根据基本算术定理:任何一个大于1的自然数N,都可以唯一分解成有限个质数的乘积 , 这里是质数,其诸方幂 是正整数。
所以, 也可以拆成一堆素数的乘积。
而且,从算数基本定理来考虑, 如果一个数字D是完全平方数,那么其所有质因子的指数都是偶数。因为,,而x可以按照基本算术定理唯一拆成限个质数的乘积。既然是唯一,那么,就是把x中有限个质数的指数乘以2,就可以得到D拆成有限个质数的形式。
比如:令x=945,D=945*945=893025。
那么可以看出来:
根据基本算术定理,945的这个分解法是唯一的。
所以 这个也是唯一的。
所以如果一个数字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互质时,进一步有
令p为题中的1,000,000,007。
考虑;
令A=a mod p, B=b mod p,C=c mod p;
则:
因为费马小定理,所以,即
即
即 ,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,依次判断是不是质数,如果为合数直接乘积。
容易知道,最后的乘积为。
然后判断 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;
}