例题一:The Luckiest number(pku 3696)
中国人喜欢的数字是6,8,9.Bob也一样,他喜欢的数字是8.Bob的幸运数字为能整除L的全8序列的最短长度。现在的任务就是找到Bob的幸运数字,如果不能找到,则输出0.
如果L=1,则幸运数字应该是一,因为8只能整除一;同理,当L=2时,幸运数字同样是一。
输入:输入数据有多组,每组占一行,每组测试数据输入一个L(1<=L<=2 000 0000 000).当L=0时,输入结束。
输出:对应每组输入数据输出占一行,输出格式为:测试数据的编号(从1开始),Bob的幸运数字。具体见样例。
8
11
16
0
Case 1: 1
Case 2: 2
Case 3: 0
分析:题意,给一个数N(1<=N<=2000000000);问是否存在N的倍数M,且M的各个位全部由8组成,如果存在多个取最小的M并输出M由几个8组成。
解题思路:因为M全部由8组成,即M=(10^x -1)*8/9=k*N;
则 (10^x-1)*8/gcd(8,N)=9*k*N/gcd(8,N);
令p=8/gcd(8,N); q=9*N/gcd(8,N); 即 (10^x-1)*p=k*q;
由于p和q互质(因为8/gcd(8,N)与N/gcd(8,N)互质) ,则(10^x-1)%q==0;
根据同余定理可知,10^x ≡1(mod q)
根据欧拉定理可知当gcd(a,b)==1时,a^φ(b)≡1(mod b);
即可得出:当gcd(10,q)==1时 10^φ(q)≡1(mod q) 即通过枚举φ(q)的因子(最小因子)就能得出结果.x=φ(q)一定是一个解,但不一定是最小的解,若x=φ(q)不是最小的解,那么最小的解x一定是φ(q)的因子.
证明:假设x是满足2^x≡1(mod n)的最小的x,假设x不是φ(n)的因子,令r=φ(n)mod x,则r>0&&r<x.由2^φ(n)≡ 1(mod n)和2^x≡1(mod n),可以得2^r mod n=1,则存在一个比x还小的数,使得2^r≡1(mod n),这与假设矛盾,所以x为φ(n)因子。
最后检查模是否为1即可。具体求解步骤如下:
(1)求解x=φ(q)
(2)找出φ(q)的所有素因子Pi;
(3)让x=x/pi,直到10^x! ≡1(mod q),或pi不能整除x,如果10^x!≡ 1(mod m),x=x*pi;
(4)重复(3),直到所有的素因子pi都经过(3)处理;
(5)x就是满足10^x≡1(mod m)最小的x
那么何时没有解呢?那就是gcd(10,m)!=1时,证明:
1.若d=2,q=2*k,那么10^x=2^x*5^x=1%2k
即2^x*5^x=1+2k*m,左边为偶数,右边为奇数,显然矛盾。
2.若d=5,q=5*k,那么10^x=2^x*5^x=1%5k
即2^x*5^x=1+5k*m,左边是5的倍数,右边不是5的倍数,显然矛盾。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
long long gcd(long long a,long long b)///最大公因数
{
if(b==0)
return a;
return gcd(b,a%b);
}
long long mo;
long long multi(long long a,long long b)///(a*b)%mo
{
long long n=0;
while(b)
{
if(b&1)
{
n=(n+a)%mo;
b--;
}
b>>=1;
a=(a<<1)%mo;
}
return n;
}
long long quick_mod(long long x)///x为幂的值
{
long long ans=1;
long long m=10;
while(x)
{
if(x&1)
{
ans=multi(ans,m);
x--;
}
x>>=1;
m=multi(m,m);
}
return ans;
}
int main()
{
int t=0;
while(scanf("%lld",&mo)!=EOF)
{
if(mo==0)
break;
printf("Case %d: ",++t);
mo=mo*9/gcd(mo,8);
if(gcd(mo,10)!=1)///解不存在的条件
{
printf("0\n");
continue;
}
long long rear=mo,n=mo;
long long prime[50][2];
int k=0;///k记录了素因子的个数
for(long long i=2;i*i<=n;i++)///求的是mo的欧拉函数
if(n%i==0)
{
rear=rear-rear/i;
while(n%i==0)
n/=i;
}
if(n>1)
rear=rear-rear/n;
n=rear;
for(long long i=2;i*i<=n;i++)///求的是欧拉函数phi[mo]的素因子及其个数
if(n%i==0) ///用二维数组的对应关系存储素因子及其个数
{
prime[k][0]=i;
prime[k][1]=0;
while(n%i==0)
{
n/=i;
prime[k][1]++;
}
k++;
}
if(n>1)
{
prime[k][0]=n;
prime[k][1]=1;
}
for(int i=0;i<=k;i++)///素因子的个数
for(int j=1;j<=prime[i][1];j++)///每个素因子的个数
if(quick_mod(rear/prime[i][0])==1)
rear/=prime[i][0];
printf("%lld\n",rear);
}
return 0;
}
例题二:Period of an Infinite Binary Expansion(pku 3358)
题目描述:令{x} = 0.a1a2a3.... 是一个数小数部分的二进制表示形式。假设{x}具有循环性,那么{x}就可以写成下面的形式
{x} = 0.a1a2...ar(ar+1ar+2...ar+s)w
对某个r和s成立(r>=0且s>0).(ar+1ar+2...ar+s)w 代表{x}二进制的无限循环部分。
序列x1=a1a2...ar 称为{x}的循环前缀,x2=ar+1ar+2...ar+s称为{x}的循环部分。
让|x1|和|x2|尽量小,那么x1称为最小前缀,x2称为最小循环。
例如:x=1/10=0.0001100110011(00110011)w,0001100110011是1/10的一个循环前缀,00110011是1/10的一个循环部分。
然而,1/10=0.0(0011)w ,0是1/10的最小前缀,1/10的最小循环部分从第二位开始,最小循环部分的长度为4.
把一个小于1的小数转化成二进制形式,编程找到最小循环部分的起始位置以及最小循环的长度。
输入: 每组测试数据占一行,输入整数p,q,代表了一个小数 p/q,p>=0且q>0.
输出:对应每组测试数据,输出占一行。输出两个整数,第一个整数代表最小循环的起始位置,第二个整数代表最小循环部分。具体输出格式详见样例;
1/10
1/5
101/120
121/1472
Case #1: 2,4
Case #2: 1,4
Case #3: 4,4
Case #4: 7,11
观察1/10这组数据,按照二进制转化法(乘二法),可以得到
1/10 2/10 4/10 8/10 16/10 32/10.。。。。
然后每个分子都尽可能减去10,得到
1/10 2/10 4/10 8/10 6/10 2/10.。。。
这时出现了重复,那么这个重复,就是要求的最小循环。
抽象出模型如下,对于p/q,首先将其化简成最简形式,p' =p/gcd(p,q),q'=q/gcd(p,q). p' * 2^i≡p' * 2^j(mod q').经过变换得到, p' * 2^i * ( 2^(j-i) -1 )≡0(mod q'),也就是q' | p' * 2^i * ( 2^(j-i) -1 )由于gcd(p',q')=1,故有q' | 2^i * ( 2^(j-i) -1 ).
因为2^(j-i)-1为奇数,所以q'有多少个2的幂,i的值就是多少了,而且i就是循环开始位置的前一位。令q''为q'除去2^i之后的数,此时有q'' | ( 2^(j-i) -1 ),也就是求出x,使得2^x≡1(mod q'').
由于q''和2互素,所以(由欧拉定理:2^Φ(q'')≡1(mod q'') )此方程必有解,且Φ(q'')为方程的一个解。但不一定是最小解。这里和POJ3696有点相似,可知最小解为Φ(q'')的一个因子。
解题步骤如下:
(1)求解x=Φ(q'');
(2)找出Φ(q'')的所有素因子pi;
(3)令x=x/pi,直到2^x≠1(mod q'')或pi不能整除x。如果2^x≠1(mod q''),令x=x×pi;
(4)重复步骤(3),直到所有的素因子都经过(3)处理;
(5)此时x就是满足2^x≡1(mod q'')的最小解。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int p,q;
int gcd(int a,int b)
{
if(b==0)
return a;
return gcd(b,a%b);
}
int phi(int n)///求n的欧拉函数的值
{
int rea=n;
for(int i=2;i*i<=n;i++)
if(n%i==0)
{
rea=rea-rea/i;
while(n%i==0)
n/=i;
}
if(n>1)
rea=rea-rea/n;
return rea;
}
int prime[50][2];///第一列存储素因子,第二列存储素因子的个数
int ct;///记录素因子的个数
void make_prime(int n)
{
ct=0;
for(int i=2;i*i<=n;i++)///基本的找出素因子的方法
if(n%i==0)
{
prime[ct][0]=i;
prime[ct][1]=0;
while(n%i==0)
{
prime[ct][1]++;
n/=i;
}
ct++;
}
if(n>1)
{
prime[ct][0]=n;
prime[ct++][1]=1;
}
}
long long quick_mod(long long b,long long m)///快速幂取模
{
long long ans=1;
long long a=2;
while(b)
{
if(b&1)
{
ans=ans*a%m;
b--;
}
b>>=1;
a=a*a%m;
}
return ans;
}
int main()
{
int t=1;
while(scanf("%d/%d",&p,&q)!=EOF)
{
printf("Case #%d: ",t++);
if(p==0)///特殊情况处理
{
printf("1,1\n");
continue;
}
int g=gcd(p,q);
p/=g;
q/=g;
int x=0;
while((q&1)==0)
{
q>>=1;
x++;
}
x++;
int rear=phi(q);
make_prime(rear);
for(int i=0;i<ct;i++)///x可能为某个因子,所有的因子都是由素因子组合而来
for(int j=1;j<=prime[i][1];j++)
if(quick_mod((long long)rear/prime[i][0],(long long)q)==1)
rear/=prime[i][0];
printf("%d,%d\n",x,rear);
}
return 0;
}