【已整理】【数论】【总】【1】

1.有一类题目问你一个超级大的数的最高x位是什么,这种题目肯定要用log10来做,

对于整个答案的计算公式进行log10化简,拆成不会爆double的几部分相加,得到res

res-=(double)((int)res);

printf("%d",pow(10,res)*pow(10,n-1));

2.求n!%mod

最简单的方法,打表找规律

 4.light OJ 1236

题意:10000个询问,每次询问给出一个n(1e14),计算下列程序的值

long long pairsFormLCM( int n ) {
    long long res = 0;
    for( int i = 1; i <= n; i++ )
        for( int j = i; j <= n; j++ )
           if( lcm(i, j) == n ) res++; // lcm means least common multiple
    return res;
}

对n进行质因数分解,统计出每个质因数的个数,注意程序问的是无序对,而比较方便计算的是有序对。分别考虑每一个质因数,假设第i个质因数的贡献度为ai,则有(ai+1)(ai+1)-(ai*ai)=2ai+1种组合方式,则最终有序对的个数为π(2ai+1),除了(n,n),所有的无序对均被计算了两次,所以最终的答案要加1除以二。由于时间紧迫,代码就不写了,贴一份别人的代码在这里。

#include<stdio.h>  
#include<string.h>  
const int MAXN=10000100;  
bool vis[MAXN];  
long long prime[MAXN/10];  
int tot=0;  
void getPrime()  
{  
    for(long long i=2;i<MAXN;i++)  
        if(!vis[i])  
        {  
            prime[tot++]=i;  
            for(long long j=i*i;j<MAXN;j+=i) vis[j]=true;  
        }  
}  
int a[1000];//保存素因子  
int b[1000];//保存素因子的个数  
int cnt;  
void sbreak(long long n){//进行素因子分解  
    memset(a,0,sizeof(a));  
    memset(b,0,sizeof(b));  
    cnt=0;  
    for(int i=0;prime[i]*prime[i]<=n;i++){  
        if(n%prime[i]==0){  
            a[cnt]=prime[i];  
            while(n%prime[i]==0){  
                b[cnt]++;  
                n/=prime[i];  
            }  
            cnt++;  
        }  
    }  
    if(n!=1){  
        a[cnt]=n;  
        b[cnt++]=1;  
    }  
}  
int main(){  
    int T,kase=0;  
    long long n;  
    scanf("%d",&T);  
    getPrime();  
    while(T--){  
        scanf("%lld",&n);  
        sbreak(n);  
        long long ans=1;  
        for(int i=0;i<cnt;i++)  ans=ans*(b[i]*2+1);  
        ans=(ans+1)/2;    
        printf("Case %d: %lld\n",++kase,ans);  
    }  
    return 0;  
}  
View Code

 5.light oj 1220

给出一个整数n,求这个整数n能表示成的最大幂次。整数!整数!整数!而不是正整数。要考虑负数啊亲!

我的做法是,枚举1到根号n的数,看是不是因子,如果是因子,再看能否表示成这个因子的幂次。检查能否表示成这个因子的幂次的方法是log一下再pow一下,log采用换底公式,并四舍五入后取整,然后quick_pow一下看是不是等于n

这么做本质还是暴力算法,真正涉及到数论知识的解法如下

将n进行素因数分解,得到每个素因数的个数,素因数的个数的最大公因数就是最大的p,这是对于正数的情况。

如果是负数,则底数必须是个负数,且指数必须是奇数,因此我们得先把所有的因数个数化为奇数,比如现在有8个因数2,则化为1个因数256,现在有6个因数3,化为3个因数9,以此类推,我们只关心因数的个数,因数是什么我们并不关心,所以编程可以简化。

不开long long int过不了是什么鬼。。题目明明都说了是int范围内的

#include <cmath>
#include <cstring>
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>
#include <string>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long ll;

bool vis[100010];
ll prime[10001];
int tot;
ll ddiv[10001];
int cnt;

void init(){
    tot=0;
    memset(vis,false,sizeof(vis));
    for(ll i=2;i<100010;i++){
        if(!vis[i]){
            prime[tot++]=i;
            for(ll j=i;j<100010;j+=i)
                vis[j]=true;
        }
    }
}

void decap(ll n){
    n=abs(n);
    memset(ddiv,0,sizeof(ddiv));
    cnt=0;
    for(int i=0;i<tot&&prime[i]<=n;i++){
        if(n%prime[i]==0){
            while(n%prime[i]==0){
                ddiv[cnt]++;
                n/=prime[i];
            }
            cnt++;
        }
    }
    if(n>1)
        ddiv[cnt++]=1;
}

ll gcd(ll a,ll b){
    if(a<b) swap(a,b);
    if(a%b==0)  return b;
    return gcd(b,a%b);
}

int main(){
    int t;
    ll n;
    init();
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        scanf("%lld",&n);
        decap(n);
        if(n<0){
            for(int i=0;i<cnt;i++){
                while(ddiv[i]%2==0)
                    ddiv[i]/=2;
            }
        }
        ll res=ddiv[0];
        for(int i=1;i<cnt;i++)
            res=gcd(res,ddiv[i]);
        printf("Case %d: %lld\n",cas,res);
    }
    return 0;
}
View Code

 6.light oj 1197

问一个长度不超过100000的区间内有多少个质数,区间的端点范围为10^9。如果区间范围再小一点,则可以预处理一波,然后查询是O(1)的。但是现在肯定不能预处理了,预处理会同时TLE和MLE。

这时要用到素数筛选法,以及一个结论,一个合数n的最小质因数p一定小于等于n的平方根。首先一个合数一定可以拆成大于等于两个质因数的乘积,若p是最小质因数,且p大于n的平方根,则p和次小质因数的乘积就已经超过了n,矛盾,所以一个合数n的最小质因数p一定小于等于n的平方根。所以我们只需要枚举2到根号b之间的素数去筛选[a,b]区间的数就行了。

#include <cmath>
#include <cstring>
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>
#include <string>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long ll;

bool vis[100010];
ll prime[10001];
int tot;

void init(){
    tot=0;
    memset(vis,false,sizeof(vis));
    for(ll i=2;i<100010;i++){
        if(!vis[i]){
            prime[tot++]=i;
            for(ll j=i;j<100010;j+=i)
                vis[j]=true;
        }
    }
}

int main(){
    int t;
    ll a,b;
    init();
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        memset(vis,false,sizeof(vis));
        scanf("%lld%lld",&a,&b);
        ll bound=(sqrt(b)+0.5);
        for(int i=0;i<tot&&prime[i]<=bound;i++){
            ll tt=a/prime[i];
            ll tmp=prime[i]*tt;
            while(tmp<a||tmp==prime[i])
                tmp+=prime[i];
            while(tmp<=b){
                if(tmp>=a)
                    vis[tmp-a]=true;
                tmp+=prime[i];
            }
        }
        ll res=0;
        for(int i=a;i<=b;i++){
            if(i==1)    continue;
            if(!vis[i-a])
                res++;
        }
        printf("Case %d: %lld\n",cas,res);
    }
    return 0;
}
View Code

 7.light oj1138

10000个询问,每次询问一个数,求出阶乘的末尾包含这么多0的最小数。

首先反过来思考,给你一个数n,计算n!末尾有多少个0,也就意味着求从1-n这些数一共有多少个因子5。我的算法很暴力,因为题目询问的最大数就是1e8,所以我先暴力模拟,看n为多大时,结果为1e8,模拟结果大约为4e8,且模拟时间大约只有700毫秒,题目给的限时为2000毫秒,一共10000个询问,我先把询问都保存下来,然后按照大小排序,然后从小到大模拟一遍,保存结果,最后再输出,就可以过这题。下面给出代码。

#include <cmath>
#include <cstring>
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>
#include <string>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <ctime>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long ll;

const int MAXN=10005;
ll a[MAXN];
int pos[MAXN];
ll ans[MAXN];

bool cmp(int x,int y){
    return a[x]<a[y];
}

int main(){
    int t;
    scanf("%d",&t);
    for(int i=0;i<t;i++){
        scanf("%d",a+i);
        pos[i]=i;
    }
    sort(pos,pos+t,cmp);
    ll res=0;
    ll cur=0;
    for(int i=0;i<t;i++){
        while(res<a[pos[i]]){
            cur+=5;
            int tmp=cur;
            while(tmp%5==0){
                tmp/=5;
                res++;
            }
        }
        ans[pos[i]]=(res==a[pos[i]]?cur:-1);
    }
    for(int i=0;i<t;i++){
        if(ans[i]==-1)
            printf("Case %d: impossible\n",i+1);
        else
            printf("Case %d: %lld\n",i+1,ans[i]);
    }
    return 0;
}
View Code

网上的题解大约都是二分答案来计算,也就是他们都掌握了算n!末尾有多少个0,或者1-n有多少个因子5的秘籍。

其实方法不难。。你想,1-n里面有多少个数能被5整除?n/5。1-n里面有多少个数能被25整除?n/25。1-n里面有多少个数能被125整除?n/125。以此类推。所以用代码就可以巧妙的写成以下这样

long long sum(long long n)
{
    long long ans=0;
    while(n)
    {
        ans+=n/5;
        n/=5;
    }
    return ans;
}

贴上别人的完整代码

#include<stdio.h>
long long sum(long long n)
{
    long long ans=0;
    while(n)
    {
        ans+=n/5;
        n/=5;
    }
    return ans;
}
int main()
{
    int t,k=1;
    long long q;
    scanf("%d",&t);
    while(t--)
    {
        long long ans=0;
        scanf("%lld",&q);
        long long left=1,right=1000000000000;
        while(left<=right)
        {
            long long mid=(left+right)>>1;
            if(sum(mid)==q)
            {
                ans=mid;
                right=mid-1;
            }
            else if(sum(mid)>q)
            right=mid-1;
            else
            left=mid+1;
        }
        printf("Case %d: ",k++);
        if(ans)
        printf("%lld\n",ans);
        else
        printf("impossible\n");
    }
    return 0;
}
View Code

 8.uva11426 欧拉函数

题意:给出一个数n,n的范围4000001,求出如下代码所示的值

我算是想到了一些正确思路吧。。。S[n]=S[n-1]+gcd(1,n)+gcd(2,n)+……+gcd(n-1,n)。然后就不会了。。。

学会了这个之后,一开始的大体思路也是正确的,处理出前缀和后,实现O(1)的查询

但是在求每个n的值时,我要遍历所有1到根号n的数,以判断是否是因数

这样就会TLE

以后如果碰到这种情况会TLE,就从小到大递推,每次更新所有的倍数,这样就省去了很多无用判断

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
using namespace std;

typedef long long int ll;
const int MAXN=4000002;

int phi[MAXN];
void phi_init(){
     phi[1]=1;
     for(int i=2;i<MAXN;i++)
       phi[i]=i;
     for(int i=2;i<MAXN;i++)
        if(phi[i]==i)
           for(int j=i;j<MAXN;j+=i)
              phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出
}

ll sum[MAXN],f[MAXN];
void init(){
    phi_init();
    memset(f,0,sizeof(f));
    for(int i=1;i<MAXN;i++){
        for(int j=i+i;j<MAXN;j+=i){
            f[j]+=i*phi[j/i];
        }
    }
    sum[0]=0;
    for(int i=1;i<MAXN;i++)
        sum[i]=sum[i-1]+f[i];
}

int main(){
    init();
    ll n;
    while(scanf("%lld",&n)){
        if(n==0)    break;
        printf("%lld\n",sum[n]);
    }
    return 0;
}

int get_phi(int n){
    int res=n,a=n;
    for(int i=2;i*i<=a;i++){
        if(a%i==0){
            res=res/i*(i-1);//先进行除法是为了防止中间数据的溢出
            while(a%i==0) a/=i;
        }
    }
    if(a>1) res=res/a*(a-1);
    return res;
}
View Code

一个数n的欧拉函数值指的是小于n的数中有多少个数和n互质

 欧拉函数值打表模板如下

int phi[MAXN];
void phi_init(){
     phi[1]=1;
     for(int i=2;i<MAXN;i++)
       phi[i]=i;
     for(int i=2;i<MAXN;i++)
        if(phi[i]==i)
           for(int j=i;j<MAXN;j+=i)
              phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出
}

欧拉函数值直接计算模板如下

int get_phi(int n){
    int res=n,a=n;
    for(int i=2;i*i<=a;i++){
        if(a%i==0){
            res=res/i*(i-1);//先进行除法是为了防止中间数据的溢出
            while(a%i==0) a/=i;
        }
    }
    if(a>1) res=res/a*(a-1);
    return res;
}

欧拉函数值直接计算更快的方法

const int MAXN=33333;
bool vis[MAXN];
int prime[MAXN];
int tot;

void get_prime(){
    memset(vis,false,sizeof(vis));
    tot=0;
    for(int i=2;i<MAXN;i++){
        if(!vis[i])
            prime[tot++]=i;
        for(int j=0;j<tot;j++){
            if(1LL*i*prime[j]>=1LL*MAXN)
                break;
            vis[i*prime[j]]=true;
            if(i%prime[j]==0)
                break;
        }
    }
}

int get_phi(int x)
{
    int res=x;
    for(int i=0;i<tot&&prime[i]*prime[i]<=x;i++)
    {
        if(x%prime[i]==0)
        {
            res=res/prime[i]*(prime[i]-1);
            while(x%prime[i]==0)
            {
                x/=prime[i];
            }
        }
    }
    if(x>1) res=res/x*(x-1);
    return res;
}

欧拉函数学习

定理

 

  • 定理0 算术函数f如果满足对于任意两个互质的正整数m和n,均有f(mn)=f(m)f(n),就称f为积性函数(或乘性函数)。

 

如果对于任意两个正整数m和n,均有f(mn)=f(m)f(n),就称为完全积性函数。

 

  • 定理1 对于素数p,ϕ(p)=p−1。
  • 定理2 ϕ(pn)=pn−pn−1,因为素数幂pn不互质的只有p的倍数,一共有pn/p=pn−1个。
  • 定理3 若m、n互质,ϕ(mn)=ϕ(m)ϕ(n),所以欧拉函数是积性函数。

 

因为mn互质,和m互质的数乘上和n互质的数就会和mn互质。

 

  • 定理4 设n=p1a1p2a2...pkak为正整数n的素数幂分解,那么ϕ(n)=n(1−1/p1)(1−1/p2)...(1−1/pk)。

 

由定理2,ϕ(pn)=pn−pn−1=pn (1-1/p),又由定理3,ϕ(n)=p1a1p2a2...pkak(1−1/p1)(1−1/p2)...(1−1/pk)=n(1−1/p1)(1−1/p2)...(1−1/pk)

 

  所以可以方便地求欧拉函数:边找质因子边算,res=n,找到一个质因子p,res=res/p*(p-1)。

  • 定理5 设n是一个大于2的正整数,那么ϕ(n)是偶数。


对于素数p,ϕ(p)=p−1,大于2的素数是奇数,那么它的欧拉函数就是偶数。

对于合数n,

  由定理2,ϕ(pk)=pk−pk−1

  p=2时,ϕ(2k)=2k−2k−1=2k−1是偶数,

  对于大于2的素数p,ϕ(pk)=pk−pk−1,p为奇数,p的次方也为奇数,两个奇数相减为偶数。

  所以ϕ(pk)为偶数。

  又由定理3,ϕ(n)=ϕ(p1a1)ϕ(p2a2)...ϕ(pkak),所以ϕ(n)一定是偶数。

  • 定理6 ∑d|nϕ(d)=n。

d|nϕ(d)表示所有n的约数的欧拉函数值求和,Cd是gcd(n,x)==d的x(1≤x≤n)的集合,d不同的Cd集合不相交。

  若m∈Cd,则gcd(n,m)=d,所以gcd(n/d,m/d)=1,由于1≤m≤n,所以1≤m/d≤n/d,所以m/d可以取小于n/d且与n/d互质的所有数,m和m/d个数相同,所以m的个数就是ϕ(n/d),也就是|Cd|=ϕ(n/d)。1到n每个数和n的最大公约数一定是n的约数,那么所有集合Cd的所有元素个数之和就是n,也就是∑d|n|Cd|=n。所以∑d|nϕ(d)=n。

 

9.uva11752

输出unsigned long long int 范围内所有的super power数,super power数定义为至少能表示成两种幂次的数,1也是,因为1能表示成1的任意幂次

本题没有输入,只有输出,输出需升序

总之还是学到了很多吧,找的时候只要找合数次方就行了,然后估计几个范围,用set记录(自动去重即可)。

一个主要的难点是边界问题,像这种一旦乘上去就会越界的情况,用除法提前判断!!!

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>
using namespace std;
typedef unsigned long long int ll;
bool vis[101];
set<ll> S;

void init(){
    ll prbound=101;
    for(int i=2;i<prbound;i++){
        if(!vis[i]){
           for(int j=i+i;j<prbound;j+=i)
                vis[j]=true;
        }
    }
}

int main(){
    init();
    ll intlim=(1<<64)-1;
    for(ll i=2;i<=65535;i++){
        ll ans=i;
        for(ll j=2;j<=64;j++){
            ans*=i;
            if(vis[j])
                S.insert(ans);
            if(ans>intlim/i)
                break;
        }
    }
    S.insert(1);
    for(set<ll>::iterator it=S.begin();it!=S.end();it++){
        printf("%llu\n",*it);
    }
    return 0;
}
View Code

11.poj2378

水题一道,求phi的前缀和(也不完全是,稍微改写了一下,注意第二个循环中的第二层循环会改变phi[i],所以累加得放在最后)

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

typedef long long int ll;
const int MAXN=1000005;

int phi[MAXN];
ll sum[MAXN];
void phi_init(){
     phi[1]=0;
     sum[1]=0;
     for(int i=2;i<MAXN;i++)
       phi[i]=i;
     for(int i=2;i<MAXN;i++){
        if(phi[i]==i)
           for(int j=i;j<MAXN;j+=i)
              phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出
        sum[i]=phi[i]+sum[i-1];
     }
}

int main(){
    phi_init();
    int p;
    while(scanf("%d",&p)){
        if(!p)  break;
        printf("%lld\n",sum[p]);
    }
    return 0;
}
View Code

 12.light oj1370

也是一道欧拉函数的水题。。。然而自己愣是没过。

首先质数x的欧拉函数值为x-1,且质数的分布不会太稀疏,我们先打出欧拉函数的表,然后从询问的数开始枚举,直到欧拉函数值大于给定的数即可。

因为质数的分布不会太稀疏,所以不会T

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

typedef long long int ll;
const int MAXN=3333333;

int phi[MAXN];
void phi_init(){
     phi[1]=0;
     for(int i=2;i<MAXN;i++)
       phi[i]=i;
     for(int i=2;i<MAXN;i++)
        if(phi[i]==i)
           for(int j=i;j<MAXN;j+=i)
              phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出
}

int main(){
    phi_init();
    int n,t,p;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        scanf("%d",&n);
        ll res=0;
        for(int i=0;i<n;i++){
            scanf("%d",&p);
            for(int j=p+1;;j++){
                if(phi[j]>=p){
                    res+=j;
                    break;
                }
            }
        }
        printf("Case %d: %lld Xukha\n",cas,res);
    }
    return 0;
}
View Code

13.poj 2891 线性同余方程组 模板题

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

typedef long long int ll;


void exgcd(ll a,ll b,ll &d, ll &x,ll &y){///扩展欧几里德算法
    if(!b){x=1;y=0;d=a;}
    else{exgcd(b,a%b,d,y,x);  y-=x*(a/b);}
}

ll m;///m代表方程组的数量
ll a[500005],b[500005];///x%a[i]=b[i]
///求解同余方程组
bool Chi_r(ll &minnum,ll &mod){///minnum表示最小正整数,mod表示模
    ll m1=a[0],b1=b[0],x,y,d;
    for(ll i=1;i<m;++i){
        ll m2=a[i],b2=b[i];
        exgcd(m1,m2,d,x,y);
        if((b2-b1)%d!=0) return false;///不存在解
        ll t=m2/d;
        x=(x*((b2-b1)/d)%t+t)%t;
        b1=m1*x+b1;
        m1=m1*(m2/d);
    }
    minnum=b1;
    mod=m1;
    return true;///存在解
}

int main(){
    while(~scanf("%lld",&m)){
        for(ll i=0;i<m;i++)
            scanf("%lld%lld",a+i,b+i);
        ll x,y;
        if(Chi_r(x,y))
            printf("%lld\n",x);
        else
            printf("-1\n");
    }
    return 0;
}
View Code

14.Product it again

这题在看了一句话点拨之后自己做了出来,甚是开心哈哈哈哈哈哈哈哈!- -虽然其实也蛮水的一题

总之!一定想到转化问题转化问题转化问题!!!一开始一直在想欧拉函数,但是一直没做出来,如果能想到计算每个素因数的贡献度,这道题目的难度就减少了一大半了!枚举每个质因数即可!方法看代码吧,我是找规律找出来的,能够想到这种正确的方法,主要是想起了之前做的一题,n!中有多少个5因子。这题也是类似的方法!

比如2,n/2为1到n中2的倍数有多少个,m/2类同。则这两个数相乘可以计算得出一部分的2的贡献。n/2/2,m/2/2则是4的倍数的个数,这两个数相乘也可以得出一部分的2的贡献。

举个具体的例子,3和6。5/2=2,6/2=3。2*3=6,表示贡献了6个2。这6个2包括 (2,2)的一个2,(2,4)的一个2,(4,2)的一个2,(4,4)的其中一个2,(2,6)的一个2,(4,6)的一个2。2/2=1,3/2=1。1*1=1,表示贡献了1个2,这一个2就是(4,4)的另一个2。有这种性质的一个重要原因就是公因数取得是2的幂的较小值。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

typedef long long int ll;
const ll mod=1e9+7;
const ll MAXN=10000005;
ll prime[MAXN/10];
bool vis[MAXN];
ll cnt;
ll n,m;

ll quick(ll num,ll k){
    ll res=1;
    while(k){
        if(k&1)
            res=(res*num)%mod;
        num=(num*num)%mod;
        k>>=1;
    }
    return res;
}

void get_prime(){
    cnt=0;
    memset(vis,false,sizeof(vis));
    for(ll i=2;i<MAXN;i++){
        if(!vis[i]){
            prime[cnt++]=i;
            for(ll j=i+i;j<MAXN;j+=i)
                vis[j]=true;
        }
    }
}

int main(){
    int t;
    get_prime();
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        ll res=1;
        scanf("%lld%lld",&n,&m);
        ll bound=min(n,m);
        for(ll i=0;i<cnt;i++){
            ll tmp=0;
            if(prime[i]>bound)  break;
            ll tn=n,tm=m;
            while(tn!=0&&tm!=0){
                tn/=prime[i];
                tm/=prime[i];
                tmp+=tn*tm;
            }
            res=(res*quick(prime[i],tmp))%mod;
        }
        printf("%lld\n",res);
    }
    return 0;
}
View Code

 15.Codeforces 834C. The Meaningless Game

题意,两个人,一场比赛有若干轮,两人的初始比分均为1,每一轮随机的有一个胜者和败者,且随机挑一个正整数k,赢得一方分数乘以k方,输的一方分数乘以k。给出若干场比赛的结果,让你判断每场比赛的比分是否合法。

一开始暴力分解T了,估计一下时间复杂度确实会T。确实是算法不够快。由于每个质因数的判断方法是一致的:对于一个质因数,a和b中其个数和为3的倍数,且a和b中该质因子的个数不能超过彼此的两倍。既然这样的话,我们可以尝试能否统一判断每一个质因数。对于总和为3的倍数很好判断啊!a和b相乘的结果如果是一个立方数,那就是每个质因数的个数都是3的倍数啊!对于立方,直接开立方会有精度问题,所以我们可以预处理并用map记录所有的立方数。然后怎么判断a和b中每个质因子的个数有没有超过彼此的两倍呢?

我们考察某一个质因数,a中有x个,b中有y个。那么必须满足2*x>=y且2*y>=x。第一个式子两边加x,第二个式子两边加y。得到3*x>=x+y,3*y>=x+y。再两边除以3,得到x>=(x+y)/3,y>=(x+y)/3。在前面的基础上,3一定能够整除x+y。所以无需担心这一点,那么这两个不等式意味着什么呢?a*b开立方后得到的数记为c,这两个不等式意味着a%c==0和b%c==0。。。所以整到题目就是这么简单

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;

typedef long long int ll;
const ll MAXN=33333;
map<ll,ll> mp;

void init(){
    for(ll i=1;i<=1000010;i++){
        ll tmp=i*i*i;
        mp[tmp]=i;
    }
}
//#define DEBUG
int main(){
#ifdef DEBUG
    freopen("in.txt","r",stdin);
#endif // DEBUG
    ll n;
    ll a,b;
    init();
    scanf("%I64d",&n);
    for(ll i=0;i<n;i++){
        scanf("%I64d%I64d",&a,&b);
        ll tmp=a*b;
        if(!mp.count(tmp)){
            printf("No\n");
            continue;
        }
        tmp=mp[tmp];
        if(a%tmp==0&&b%tmp==0)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}
View Code

 16.如何求一个数的因子有多少个?

对这个数进行素因数分解,假设共有k个因子,每个因子的个数为a1,a2,……,ak个,那么这个数的因子有(1+a1)*(1+a2)*……*(1+ak)个

 17.给你一个区间,让你对这个区间内的所有数进行素因数分解

如果对于每个数进行素因数分解,要枚举每个素数,这样对于每个复杂度是根号的,比较高,如果我们利用筛法来用素数去找这个区间内的哪些数含有这个素因数,分摊下来复杂度会比较低

19.一些组合恒等式

20.非降路径问题

这个没有任何限制的问题很简单,就是一共有m+n步,从中选出m步向右即可

由于m<n,所以从1,0开始走的路径一定会穿过y=x这条线

 21.poj2773。给你一个数n,求第k大的与n互质的数。k的范围很大。

一开始我就想到了欧拉函数,然后如果phi[n]>=k,直接暴力算。但是如果phi[i]<k,我就不知道怎么办了。这种情况下千万不要放弃,先看一下有没有规律!!!

所以这里确实有一个很重要的规律,对于一个数n,所有小于n的与n互质的数组成一个循环节,后面的数都是循环的!!!由小于n的与n互质的数可以递推出所有与n互质的数。

#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <iostream>
using namespace std;
typedef long long int ll;
const int MAXN=1000005;

int phi[MAXN];
void phi_init(){
     phi[1]=1;
     for(int i=2;i<MAXN;i++)
       phi[i]=i;
     for(int i=2;i<MAXN;i++)
        if(phi[i]==i)
           for(int j=i;j<MAXN;j+=i)
              phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出
}


/*
int get_phi(int n){
    int res=n,a=n;
    for(int i=2;i*i<=a;i++){
        if(a%i==0){
            res=res/i*(i-1);//先进行除法是为了防止中间数据的溢出
            while(a%i==0) a/=i;
        }
    }
    if(a>1) res=res/a*(a-1);
    return res;
}
*/

int gcd(int a,int b){
    if(a<b) swap(a,b);
    if(a%b==0)  return b;
    return gcd(b,a%b);
}

int main(){
    phi_init();
    int n,k;
    while(~scanf("%d%d",&n,&k)){
        int cnt=phi[n];
        int times=k/cnt;
        k%=cnt;
        if(k==0){
            k=cnt;
            times--;
        }
        int counter=0;
        int res=1;
        for(int i=1;i<n&&counter<k;i++){
            if(gcd(n,i)==1)
                counter++;
            if(counter==k)
                res=i;
        }
        printf("%d\n",res+times*n);
    }
    return 0;
}
View Code

 25.给你两个数作为初始集合,每次可以从集合中挑出两个数a,b,可以选择将a+b或a-b加入集合,但要保证不重复,最后集合里的数,一定都是a,b的最大公因数的倍数

 26.从0出发,每次走n步,总共的点是0到m-1循环,那么最后可以覆盖的点的编号,一定都是n,m的最大公因数的倍数

28.原根

1)所有的奇素数都是有原根的

2)一个数n有原根,那么他有phi(phi(n))个模n不同余的原根(n是否素数都可用)

3)一个素数有原根,则有phi(n-1)个原根

poj1284 直接用这个结论即可

 没学过这个确实很难想到,而且我打了半天表之后也没发现规律,题目给了三个样例,答案都是偶数,那么就应该想到欧拉函数了!因为欧拉函数值n>2时一定是偶数!所以应该打个欧拉函数表看下有没有规律!

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long int ll;
const int MAXN=66666;
int phi[MAXN];
void phi_init(){
     phi[1]=1;
     for(int i=2;i<MAXN;i++)
       phi[i]=i;
     for(int i=2;i<MAXN;i++)
        if(phi[i]==i)
           for(int j=i;j<MAXN;j+=i)
              phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出
}

int main(){
    int n;
    phi_init();
    while(~scanf("%d",&n)){
        printf("%d\n",phi[n-1]);
    }
    return 0;
}
View Code

 

posted @ 2017-10-18 19:33  nearlight  阅读(259)  评论(0)    收藏  举报