7.9 lrj 数学基础 (10-1~10-5

10-1 巨大的斐波那契数

Q:输入两个非负整数a、b和正整数n(0<=a,b<264,1<=n<=1000),你的任务是计算f(ab)除以n的余数,f(0) = 0, f(1) = 1,且对于所有非负整数i,f(i + 2) = f(i + 1) + f(i)。

分析:1、注意a,b的范围 ,会爆long long 要用unsigned long long;

           2、由于模数为n,又因为F[i] = (F[i-1] + F[i-2]) % n,所以F[i]只有n*n种可能(F[i-1]有n种可能,F[i-2]有n种可能),所以循环节长度比小于n*n,因此我们只要算出循环节,再用快速幂解出k = a^b%L(L为循环节长度),输出F[k]即可。

           3、循环节开头一定是从f[0] f[1] 这两项 ,也就是说,没有余项,可以用反证法利用斐波那契数列的递推性证明

代码:

// 紫书 例题10-1
#include <bits/stdc++.h>
#define N 1000050
#define MOD 1000000007
using namespace std;
typedef unsigned long long ll;
ll t ,a ,b ,n ,m ,ans ,res;
ll f[N] ,F[N];

ll init( ll n ){
    f[1] = 1%n; f[2] = 1%n;
    for( ll i=3 ;i<=n*n ;i++){
        
        f[i] = (f[i-1]+f[i-2])%n;
        /*for( ll j=2 ; j<=i-1 ;j++){
            if( f[j]==f[i] && f[j-1]==f[i-1] ){
                res = j-2;
                return i-j;
            }
         
        }*/  //不用特意寻找循环节出现位置,因为一定是从1 1 开始的
        if(f[i]==f[1] && f[i-1]==f[2])return i-2; 
    }
}
// 由于n<=1000,所以最多有1000种余数,2项排列,最多n^2项就会有重复
ll q_pow( ll a ,ll b ,ll mod){
    //cout<<a<<endl;
    a%=mod;
    
    ll ans = 1;
    while( b ){
        if( b&1 )ans = (ans*a)%mod;
        a = (a*a)%mod;
        b >>= 1;
    }
    return ans%mod;
}

int main( ){
    //freopen( "out.txt" ,"w" ,stdout );
    scanf("%lld" ,&t);
    while( t-- ){
        cin>>a>>b>>n;
        m = init( n );
        
        //ans = q_pow( a ,b ,res+m );        
        /*if( ans > res ){
            ans -=res;
            ans =  (ans-1)%m+1;
            ans +=res;
        }*/
        //不用考虑余项res,因为不存在的
         
        ans = q_pow( a ,b ,m );
        if( ans==0 )ans = m;
        printf("%lld\n" ,f[ans] );
    }    
    return 0;
} 

 

10-2(模 ,exgcd

Q :

随机选取x1,a,b,根据公式xi=(a*xi-1+b)%10001得到一个长度为2*n的序列,奇数项作为输入,求偶数项,若有多种,随机输出一组答案

即,给出已知的x1,x3,x5,x7……x2k+1,找出a和b,满足递推式,并输出x2,x4,x6……x2k  (n0<=a,b<=10000)

分析: 由于递推式对10001取模 ,所以只考虑a,b在10001以内的状况即可,接下来有两种思路:

1。暴力枚举a,b ,然后检查 ,因为a,b的范围不大,所以是可行的

//紫书 例题10-2 不爽的裁判
//暴力 ,同余定理
#include<bits/stdc++.h>
#define rep( i ,x ,y) for(int i=x ;i<=y ;i++)
using namespace std;
int t ,a ,b;
int x[1000];
 
bool check( int a){
    for( int i=0 ;i<=10000 ;i++){
        int flag = 1;
            for( int j=2 ;j<=2*t ;j++){
            if( j%2==0 )x[j] = (x[j-1]*a+i)%10001;
            else if( x[j] != (x[j-1]*a+i)%10001 ){
                    flag = 0; break;
                 }
            } 
            if(flag)return true; 
    }
    return false;
}

int main( ){
    scanf( "%d" ,&t );
    for( int i=1 ;i<=2*t-1 ;i+=2)
       scanf( "%d" ,&x[i] );
    //由于每一项对10001取余,所以a和a mod 10001 等价 ;
    // 所以只用在10000范围内枚举a 
    if(t>1)for( int i=0 ; i<=10000 ;i++ ){
        if( check(i) )break;
    }
    for( int i=2 ;i<=2*t ;i+=2){
        printf("%d\n" ,x[i] );
    }
    return 0;
}

2。枚举a ,此时 x3 =( a*x2+b )%10001= (a*a*x1+a*b+b)%10001

化为非取余的形式:  y*10001 + x3 = b*(a+1) + a*a*x1

即: (a+1)*b - 10001*y = x3 - a*a*x1

这是二元一次不定方程 ,可以用扩展欧几里得算法求解:http://www.voidcn.com/article/p-uqqufnpp-bkd.html

exgcd:

void exgcd(ll a, ll b, ll& d, ll& x, ll &y)
{
    if(!b)
    {
        d = a, x = 1, y = 0;
    }
    else
    {
        exgcd(b, a % b, d, y, x);
        y -= x * (a / b);
    }
}

其中 d 是最后求得的gcd(a ,b) ,解出来 x ,y 是ax + by = gcd(a,b) 的一组解

注意: 由于是不定方程,a,b正负关系不大 ,所以都取正值 ,最后在整理

结果可能会很大,所以一般用ll不用int

最后求出来不是原方程的解,要还原一下

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
ll x[100010] ,g ,a ,b ,t;
void gcd( ll a ,ll b ,ll &d ,ll &x ,ll &y ){
     if( !b ){ d=a ;x=1 ;y=0 ;}
     else { gcd(b ,a%b , d, y, x); y -= x*(a/b); }
}

bool check(  ){
        //cout<<a<<" "<<b<<endl;
     for( int i=2 ;i<=2*t ;i++ ){
         if( i%2 ){ if( x[i] != (a*x[i-1]+b)%10001)return false; }
         else x[i] = (a*x[i-1]+b)%10001;
     }
     return true;
}

int main( ){
    scanf("%lld" ,&t);
    for( int i=1 ;i<=2*t-1 ;i+=2 )
        scanf("%lld" ,&x[i]);

    for( a=0 ;a<10001 ;a++ ){
        ll y ,d=x[3]-a*a*x[1];
        // y的结果关系不大 ,所以那一项的系数可正可负 
        gcd( a+1 ,10001 ,g ,b ,y );
        if( d%g )continue;  //判断方程是否有解 
        b = b*d/g;  //还原一下 
        b%=10001;
        if( check(  ) )break;
    }
    for( int i=2 ;i<=2*t ;i+=2 ){
        printf( "%lld\n" ,x[i] );
    }
    return 0;
    }

 

10-3 除法(唯一分解定理与大数分解

Q:给出p,q,r,s,计算C(p,q)除以C(r,s)的结果,pqrs均小于10000的非负整数。输出除法的结果保留小数点后5位结果,结果保证不超过一亿。

A: 大数间的除法可以用唯一分解定理,就是将大数表示为质因子及其次数的形式,化简后再进行计算

 

//210ms
#include<bits/stdc++.h> #define N 10005 using namespace std; struct Prime{ int a ,p=0; //保存质因子的底数和指数 }prime[N+5]; int unprime[N+5] ,cnt = 0 ,p ,q ,r ,s; //质数打表 void geteuler( ){ unprime[1] = 1; for( int i = 2 ;i <= N ;i++ ){ if( !unprime[i] )prime[cnt++].a = i ; for( int j = 0 ;j < cnt && prime[j].a*i<=N ;j++ ){ unprime[ prime[j].a*i ] = 1; if( i%prime[j].a == 0)break; } } return ; } void pow_num(int a ,int p){ for( int i=0 ; prime[i].a<=a ;i++){ while( a%prime[i].a==0 ){ //分解质因子 a/=prime[i].a; prime[i].p += p; } if( a==1 )break; } } void pow_fac(int a ,int p ){ for( int i=1 ;i<=a ;i++) pow_num( i ,p ); } int main( ){ geteuler( ); while( ~scanf("%d%d%d%d" ,&p ,&q ,&r ,&s ) ){ pow_fac( p ,1 ); pow_fac( r ,-1); pow_fac( q ,-1); pow_fac( s ,1 ); pow_fac( p-q,-1); pow_fac( r-s ,1); double ans = 1; for( int i=0 ;i<cnt ;i++){ ans *= pow(prime[i].a ,prime[i].p ); prime[i].p = 0; } printf("%.5f\n" ,ans); } return 0; }

当然这题也可以直接手动约分组合数分子分母后计算,当然,数据更大的话就不行了,而且这里防止double爆掉,要一个大项一个小项交替相乘:

//0ms
#include<bits/stdc++.h> using namespace std; int p ,q ,r ,s; int main( ){ while( ~scanf("%d%d%d%d" ,&p ,&q ,&r ,&s) ){ p = p-q+1; r = r-s+1; double ans = 1; for( int i=1 ;i <= q || i<=s ;i++ ){ if(i <= q)ans *= 1.0*(p++)/i; if(i <= s)ans *= 1.0*i/(r++); } printf("%.5f\n" ,ans); } return 0; }

 

10-4 最小公倍数最小和(唯一分解定理与最小公倍数,贪心

Q:给定一个n(n< (1<<31)),求两个或多个整数的和,要求其最小公倍数为n,且结果为所有可能结果中最小的 ;

A:

1. 两个数的最小公倍数为n,则有必要条件:他们唯一分解后的结果的 并集 一定 包含 n唯一分解的结果;

由于最小的质数是2 ,所以对于任意质数有 a+b < a*b 

所以贪心一下,不难发现将n唯一分解后 将每一项 aipi相加即最优结果

2.考虑n==1 和 n为质数的情况

 

#include<bits/stdc++.h>
#define N 105005
using namespace std;
typedef long long ll;
ll n;
struct Prime{
    ll a ,p=0;
}prime[N+5];

int unprime[N+5] ,cnt = 0;
ll res;
void geteuler( ){
    unprime[1] = 1;
    for( int i = 2 ;i <= N ;i++ ){
        if( !unprime[i] )prime[cnt++].a = i ;
        for( int j = 0 ;j < cnt && prime[j].a*i<=N ;j++ ){
            unprime[ prime[j].a*i ] = 1;
            if( i%prime[j].a == 0)break;
        }
    }
    return ;
}

void div( ll n ){
    for( ll i=0 ;i<cnt ;i++ ){
        //cout<<i<<" "<<n<<endl;
        while( n%prime[i].a==0 ){
           prime[i].p++;
           n/=prime[i].a;
        }
        if( n==1 )break;
    }
    res = n;
}

ll q_pow( ll a ,ll p ){
    ll ans = 1;
    while( p ){
        if( p&1 )ans *= a;
        a *= a;
        p >>= 1;
    }
    return ans;
}

int main( ){
    int ks = 1;
    geteuler();
    while( ~scanf("%lld" ,&n) ,n ){
        ll ans = 0 ,tot=0;
        div( n );
        for( int i=0 ;prime[i].a<=n && i<cnt ;i++ ){
             if( prime[i].p )tot++,ans += q_pow( prime[i].a ,prime[i].p );
             prime[i].p = 0;
        }
        if(res != 1){
            tot++; ans+=res;
        }
        if( tot==1 )ans+=1;
        if( n==1 )ans=2;
        printf("Case %d: %lld\n" ,ks++,ans );
    }
    return 0;
}

 

10-5  xor gcd( xor ,gcd ,有技巧的枚举

Q:求[1,n](n<=30000000)范围内的整数队a,b(a<=b)的个数,使得 gcd(a,b) = a XOR b.

A: 1.直接枚举a,b ,O(n^2)复杂度,再加上判断,肯定过不了

    2. 异或性质1 : a^b ==c ,则 a^c ==b

由性质1 ,可以枚举a ,c 求b 代替枚举a,b 求c,又c==gcd(a,b) ,所以a>c ,且 c为a的因子 ,可以通过c用筛法枚举a ,复杂度 O(n^2)-->O(nlogn)

//埃式筛法枚举因子

    for( c=1 ;c<=N/2 ;c++){
        for( a=c+c ;a<=N ;a+=c ){ 
                    ………… //a是所有以c为因子的数,不重复枚举<a,c>
        }
    }

 3.在2的基础上判断gcd(a,b) ,复杂度O(n(logn)^2) ,会超时

void init( ){
        //会超时
    //预处理打表 
    for( c=1 ;c<=N/2 ;c++){
        for( a=c+c ;a<=N ;a+=c ){
           b = a^c;
           if(a>=b && gcd(a,b)==c ){
                  sum[a]++;
           }    
        }
    }
    //询问的是前缀和 
    for( a=2 ;a<=N ;a++){
        sum[a] += sum[a-1];
    }
}

4.异或性质2: a^b 可以放缩-->  a-b<=a^b <=a+b

可以看到,当没有进位退位的情况下,异或与+-结果是一样的,否则结果小于加法大于减法

5,由性质2:a-b<=c ,又c==gcd(a,b)时 ,c<=a-b ,所以可得c==a-b ,直接判断即可,复杂度降为O(nlogn);

可以通过打表探讨a,b,c的关系 ,进而发现上述规律

void init( ){
    //预处理打表 
    for( c=1 ;c<=N/2 ;c++){
        for( a=c+c ;a<=N ;a+=c ){
           b = a^c;
           if(a>=b && a-b==c ){
                  sum[a]++;
           }    
        }
    }
    //询问的是前缀和 
    for( a=2 ;a<=N ;a++){
        sum[a] += sum[a-1];
    }
}
posted @ 2019-07-10 20:07  易如鱼  阅读(385)  评论(0编辑  收藏  举报