您的位置:首页 > 其它

HDU-1695 GCD(容斥定理+欧拉函数)

2017-12-03 21:05 267 查看
  前述:这个题目是从我用那个自己写的代码模板以来第一次tle,因为之前用这个模板a了不少题,我还是不甘心,反复进行“优化”,最后还是失败了,于是在这个专题里面第二次打开了题解(两次都是因为位运算,学是学过,只能处理较简单的,难了一窍不通)。在题解的帮助下我发现了自己的一个重大的失误,就是我认为的优化其实增加了时间复杂度,简而言之就是在数特别小的时候,从1-sqrt(n)找到所有的素因子并将其除去这种方法要快于只对他的素因子进行判断。这里我也思考了原因,数据范围是100000,那么sqrt()之后大概要循环300次多一些
,而它有的素因子的个数大概是100000/ln100000,应该是大于300的,因此要慢一些,由于这个题要反复运行这一条,所以就把这个缺陷大大放大了,于是恍然大悟。当然,a了之后当我去仔细研读题解的那种写法的时候,当然,算是看懂了,因为懂了,发现好像和我的那种写法时间复杂度上没多少差别。好像我的还更优一些,为什么说我的更优是因为题解的想法卡时间过的,而我的想法是稳过的,后来进行了分析发现我的似乎更优一些,于是拿出来tle的代码,发现多除了一个gcd(这个gcd是因为第一次写的时候那个题目用到了,后来改成模板的时候就一直带着,到现在才发现原来没用),去掉之后马上a了。这说明我的那个模板还是可以用的,但是正好借这个契机码下另外一个代码模板,也简单分析一下。

  题目:HDU-1695 GCD

  题目大意:就是从a-b里面和c-d里面找gcd=k的数对的个数。

  解题思路:分析一下这个题目其实就是从1-b/k和1-d/k之间找不重复的互素的数对的个数。因为a和c一定是1,最后一句话说的,而右半边用了gcd的因子可提性(gcd(a*k,b*k)=k*gcd(a,b))。这样可以分成两段,这里不妨设b>d,第一段是找1-d中和d互质的数的个数,用到了欧拉函数,第二段是1-d中和d+1-b中任意一个数的互质的数的个数的和,用到了容斥定理。

  AC代码1(我自己的):

#include<iostream>

#include<cmath>

#include<cstring>

#include<cstdio>

#include<algorithm>

#define maxn 100010

using namespace std;

long long euler[maxn];

long long fac[1100];

long long box[1100];

long long n,m,t,total;

long long fun;

void geteuler(){

    long long i,j;

    for (i=1;i<maxn;i++){

        euler[i]=i;

    }

    for (i=2;i<maxn;i++){

        if (euler[i]==i){

            for (j=i;j<maxn;j+=i){

                euler[j]=euler[j]/i*(i-1);

            }

        }

    }

    for (i=2;i<maxn;i++)euler[i]+=euler[i-1];

}//这里欧拉函数存的是欧拉函数的前n项的和

void getfac(long long x){

    long long i,j;

    long long xx=x;

    total=0;

    for (i=2;i<=sqrt(x);i++){

        if (xx%i==0){

            fac[++total]=i;

            while (xx%i==0){

                xx/=i;

            }

        }

    }

    if (xx>1)fac[++total]=xx;

}

void sfind(long long x,long long y,long long z,long long val){

    long long i,j;

    for (i=x;i<=total;i++){

        if (total-i<z-y)return ;

        box[y]=fac[i];

        if (y==z){

            long long temp=1;

            for (j=1;j<=z;j++){

                temp=temp*box[j];

            }

            fun+=val/temp;

        }

        else sfind(i+1,y+1,z,val);

    }

}

long long solve(long long x){

    long long i,j;

    long long temp=1;

    long long ans=0;

    for (i=1;i<=total;i++){

        fun=0;

        sfind(1,1,i,x);

        ans+=fun*temp;

        temp=-temp;

    }

    return x-ans;

}

int main(){

    long long i,j,k,l,a,b,c,d,cas=0;

    scanf("%lld",&t);

    geteuler();

    while (t--){

        scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);

        if (k==0)printf("Case %lld: 0\n",++cas);

        else {

            n=max(b,d)/k;

            m=min(b,d)/k;

            long long ans=euler[m];

            for (i=m+1;i<=n;i++){

                getfac(i);

                ans+=solve(m);

            }

            printf("Case %lld: %lld\n",++cas,ans);

        }

    }

}

  AC代码2(题解的思路):

#include<iostream>

#include<cmath>

#include<cstring>

#include<cstdio>

#include<algorithm>

#define maxn 100010

using namespace std;

long long euler[maxn];

long long fac[1100];

long long box[1100];

long long prime[maxn];

bool visit[maxn];

long long n,m,total,tot;

long long fun;

void geteuler(){

    long long i,j;

    for (i=1;i<maxn;i++){

        euler[i]=i;

    }

    for (i=2;i<maxn;i++){

        if (euler[i]==i){

            for (j=i;j<maxn;j+=i){

                euler[j]=euler[j]/i*(i-1);

            }

        }

    }

    for (i=2;i<maxn;i++)euler[i]+=euler[i-1];

}

void getfac(long long x){

    long long i,j;

    long long xx=x;

    total=0;

    for (i=2;i<=sqrt(x);i++){

        if (xx%i==0){

            fac[total++]=i;

            while (xx%i==0)xx/=i;

        }

    }

    if (xx>1)fac[total++]=xx;

}

long long solve(){

    long long i,j,tmp;

    long long ans=0,flag;

    long long t=total;

    for(i=1;i<(1<<t);i++){

        tmp=1,flag=0;

        for(j=0;j<t;j++)

            if(i&(1<<j))

                flag++,tmp*=fac[j];

        if(flag&1)

            ans+=m/tmp;

        else

            ans-=m/tmp;

    }

    return ans;

}

int main(){

    long long i,j,k,l,t,a,b,c,d,cas=0;

    scanf("%lld",&t);

    geteuler();

    while (t--){

        scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);

        if (k==0)printf("Case %lld: 0\n",++cas);

        else {

            n=max(b,d)/k;

            m=min(b,d)/k;

            long long ans=euler[m]+m*(n-m);

            for (i=m+1;i<=n;i++){

                getfac(i);

                ans-=solve();

            }

            printf("Case %lld: %lld\n",++cas,ans);

        }

    }

}

  容斥定理两种写法的简要分析:

  两种写法在找素因子的方面是完全相同的,在利用容斥定理的时候出现了差异,我的写法的想法就是dfs找,枚举1个因子的时候所有情况的和,2个因子相乘的时候所有情况的和...而题解的想法则是用了位运算。给人的直观印象是题解的比较快,毕竟是2重循环,但是实际是前者比较快,因为前者每一次循环都对应一个因子(当然我的代码一开始不能做到,后来进行了优化),而题解的写法做不到,必须位运算满足一定性质才算找到了一个因子,题解的思路可以直接体现他的算法的时间复杂度O=(2^t-1)*t。其中,t是素因子的个数。他这个写法就是利用了位运算当中的&只有是1的地方在同一位置的时候才算是一个因子,一共可以找到2^t-1个这样的因子相乘的形式,和dfs找到的是一样的,因为对每个因子都有两种状态,选和不选,这样一共有2^t个因子,除去1,正好就是上述结果。而flag表示的是因子相乘的个数,这样就可以完成容斥定理的交叉相加减。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: