hdu 2204 Eddy's爱好(容斥原理)

题意:给你一个正整数N,确定在1到N之间有多少个可以表示成M^K(K>1)的数。

本题有多组测试数据,每组包含一个整数N,1<=N<=1000000000000000000(10^18).


解析:

        如果 A^X<n;那么i^x<n(A>i>=1)必然存在,即有A个数满足条件其x次幂<n

        把n作为double型数,对其开k次方(我们不难发现,k<61,对于题目要求的n的最大值开61次方就小于2了)得m,则不大m的正整数的k次方就都是要求的整数。如果我们把k从2到60循环并把(int)m都加起来,这个和是不是就是要求的答案呢?很明显不是,如2的4次方和4的平方都是16、2的6次方和4的平方都等于64,这样以来这个和就可能大于要求的答案了。所以k不能这样循环。我们这样想:既然2的4次方和4的平方都是16、2的6次方和4的平方都等于64,那么我们何不只计4的2次方和4的3次方不计2的4次方和2的6次方呢!即我们只需计一个数的素数次方,这样我们只用把60以内素数计下来进行计算就会去掉了不少的重复。
  是不是这样算出的结果t就是答案呢?还不是。这一点就不太好想了,让我费好长时间也找不出反例,还是同学提示才算找到了。比如64(2的2*3次方)既是4的3次方又是8的平方。这一类属于m的k*p次方型(其中k和p为素数),这样以来t的值还比答案大,我们就得去掉这一部分。我们将60以内的素数两两相乘取60以内的积的集合,再对n开方并去掉这部分的重复,这样得到t的就去掉了m的k*p次方型的数重复计算。
  看起来没有什么问题了,可是用上面的测试实例一测还是不对。这又是哪里出错了。经过以上种种出错,这次就不难想出原因了。对于m的k*p*g型的数(k、p、g都是素数),它既是(m^k)^(p*g),又是(m^p)^(k*g),同时又是(m^g)^(k*p),这样我们的上一部操作又多去掉了一些值。这里我们把其加上就是了。对于这一类型的数在10^18之内只可能有三个:2的2*3*5次方、2的2*3*7次方以及3的2*3*5次方。如果n包括这几个数,再加上包括的个数算出的t就是最终答案了。

      

#include<cstdio>
#include<iostream>
#include<math.h>
#include<string.h>
using namespace std;
typedef __int64 LL;

LL n,a[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};
LL res;

void dfs(int s,LL lcm,int bit)
{
     LL m;
     m=LL(pow(n,1.0/(lcm)));
     while(pow(m+1,0.0+lcm)<n) m++;
     m--;
     if(bit&1) res=res+m;
     else res=res-m;

     for(int i=s+1;i<17;i++)
     {
         if(lcm*a[i]<60)
         {
             dfs(i,lcm*a[i],bit+1);
         }
     }
}

int main()
{
    int i,j,t;
    while(~scanf("%I64d",&n))
    {
        res=0;
        for(i=0;i<17;i++)
        {
          dfs(i,a[i],1);
        }
        printf("%I64d\n",res+1);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值