1. 程式人生 > >HDU 2204 容斥原理(過程詳解)

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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<cmath>
#include<map>
#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



*/