HDU 2204 容斥原理(过程详解)

 

Ignatius 喜欢收集蝴蝶标本和邮票,但是Eddy的爱好很特别,他对数字比较感兴趣,他曾经一度沉迷于素数,而现在他对于一些新的特殊数比较有兴趣。 
这些特殊数是这样的:这些数都能表示成M^K,M和K是正整数且K>1。 
 正当他再度沉迷的时候,他发现不知道什么时候才能知道这样的数字的数量,因此他又求助于你这位聪明的程序员,请你帮他用程序解决这个问题。 
 为了简化,问题是这样的:给你一个正整数N,确定在1到N之间有多少个可以表示成M^K(K>1)的数。 

Input

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

Output

对于每组输入,请输出在在1到N之间形式如M^K的数的总数。 
每组输出占一行。 

Sample Input

10
36
1000000000000000000

Sample Output

4
9
1001003332

 

哎,一开始学容斥,并不知道怎么用上去,代码写起来好写,但是思路难啊。

下面开始分析:

题目意思:给你一个n,求出1-n里面,构成M^K(K>1)的数,比如1-4里面有1,4两个,1一定可以。

然后我们就去看数据范围了,1e18,很大,不可能枚举。

接着我就想,能不能分一下类,按2^k,  3^k,  4^k.......,这种,但是一想,1e18开方也还有1e9,这样分类不行

随后我就按照指数来分类,指数相同归为一类,例如(2^2, 3^2, 4^2....)归为一类,(2^3, 3^3, 4^3)归为一类,一次类推。

而且,这样分类还可以算出这一类有多少个数, 直接一个公式就行了,就是开k次方,然后-1,因为是从2开始的

某一类的个数

Num = \sqrt[n]{k}-1   其中n就是n,k是幂次数,比如n为8时,x^2 这一类有3个。

其实这个特别好推。

重要的是下面的:  去重

我们分析一下,如果不去重,只需要一直开方,直到不能开为止,然后答案累加,这样复杂度并不高,1e18次最多开60次方就没了,因为2^60=1e18,所以时间根本不用担心。

那么怎么去重呢,拿样例36来说,我们先列举出来

指数为2:   2^2    3^2      4^2     5^2      6^2

指数为3:   2^3    3^3

指数为4:   2^4

指数为5:   2^5

后面就没了

我们可以看到16这个数重复了,4^2 = 16,     2^4 = 16

这是因为指数4本来就是2的倍数,2^4 = ( 2^2 )^2

那如果我们指数只枚举质数,是不是就能避免这种情况了,而且又可以减小计算量

但是,稍微再枚举一下,又能发现问题,

像 27^2这种   27^2 = ( 3*3*3 )^2 = 3^(3*2) = 3^6 = 9^3

你虽然避免了3^6,但是还有9^3会重复。

这时候就是最难点了,需要用到容斥,个人感觉在这个最后一点上不好想

27^2 和 9^3之所以会重复,是因为它们都有一个6,就是都能凑出指数6,那么我们减去指数6所得到的个数,就是答案了。

 

再自己一个样例

n = 729

729 = 27^2 = 3^6

指数为2:   2^2    3^2      4^2     5^2      6^2     7^2     8^2     9^2............................27^2

指数为3:   2^3    3^3      4^3     5^3      6^3    7^3      8^3     9^3

指数为5:   2^5

指数为7:   2^7 

后面没有了

 

这里,我们就挑指数为2和指数为3的两类进行分析,看如何去除重复

我们首先要知道,指数为2和3的,重复的数一定可以构成指数为6的数

有 8^2 和  4^3是重复的

然后我们用n算出指数为6的个数,是1个,那么在算指数为2和3时,先+指数为2的,在+指数为3的,最后--指数为6的,这就用到了容斥原理。

所以我们开始设计算法:

①首先,我们把n的质指数求出来,质指数就是指数为质数,一个循环就可以搞定,而且非常快,几下就没了,前面分析过,就是一直开方,开到不能开为止,那么我们就把指数记录了下来。

②指数记录下来了,就要用容斥了,个人习惯用dfs,简洁一些,这时候我们还需要设计一个函数,给你一个指数值,你能算出这个符合这个指数的答案有多少个

 

需要注意一点,如果用dfs写,需要加一个条件,就是因子乘积小于60,不然就会超时,因为一个很大的数得出来的指数还是挺多的,指数组合在一起也挺多,但是很多组合都没有用,因为2^60 > 1e18,或者你加一个条件,因子最多选3个,不能选多了

 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define mem(a,val) memset(a,val,sizeof a)
#define inf 0x3f3f3f3f
#define longinf 0x3f3f3f3f3f3f3f3f
using namespace std;
#define eps 1e-7
typedef long long ll;
const int maxn = 1e3+4;
bool isprime[maxn];
double prime[maxn];
int primecnt;
double n;
ll ans;
ll a[maxn];
int cnt;
void getprime()
{
    primecnt = 1;
    mem(isprime,true);
    for( int i = 4 ; i < maxn ; i+=2 )
        isprime[i] = false;
    for( int i = 3 ; i < maxn ; i+=2 )
        if( isprime[i] )
            for( int j = i<<1 ; j < maxn ; j+=i )
                isprime[j] = false;

    fori(2,maxn-1)
        if( isprime[i] )
            prime[primecnt++] = i;
}
ll f( double x )
{
    ll temp = pow(n,1/x)+eps;
    return temp-1;
}
void dfs( int start,int times,int goal,double val )
{
    if( val > 60 )
        return;
    if( times == goal )
    {
        if( times&1 )
            ans += f(val);
        else ans -= f(val);
        return;
    }
    for( int i = start ; i < cnt ; i++ )
        dfs(i+1,times+1,goal,val*a[i]);
}
int main()
{
    getprime();
    while( scanf("%lf",&n) == 1 )
    {
        ans = 0;
        cnt = 1;
        fori(1,primecnt-1)
        {
            ll k = f(i);
            if( k == 0 )
                break;
            a[cnt++] = prime[i];
        }
        fori(1,cnt-1)
            dfs(1,0,i,1);
        printf("%lld\n",ans+1);
    }
	return 0;
}
/*

16 4
2 5 6 9

19 3
2 3 4



*/

 

 

 

 

 

 

你可能感兴趣的:(数学基础)