1. 程式人生 > >BZOJ 2440 完全平方數 (容斥+莫比烏斯反演+二分)

BZOJ 2440 完全平方數 (容斥+莫比烏斯反演+二分)

2440: [中山市選2011]完全平方數

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1673  Solved: 799
[Submit][Status][Discuss]

Description

小 X 自幼就很喜歡數。但奇怪的是,他十分討厭完全平方數。他覺得這些
數看起來很令人難受。由此,他也討厭所有是完全平方數的正整數倍的數。然而
這絲毫不影響他對其他數的熱愛。
這天是小X的生日,小 W 想送一個數給他作為生日禮物。當然他不能送一
個小X討厭的數。他列出了所有小X不討厭的數,然後選取了第 K個數送給了
小X。小X很開心地收下了。
然而現在小 W 卻記不起送給小X的是哪個數了。你能幫他一下嗎?

Input

包含多組測試資料。檔案第一行有一個整數 T,表示測試
資料的組數。
第2 至第T+1 行每行有一個整數Ki,描述一組資料,含義如題目中所描述。 

Output

含T 行,分別對每組資料作出回答。第 i 行輸出相應的
第Ki 個不是完全平方數的正整數倍的數。

Sample Input

4
1
13
100
1234567

Sample Output

1
19
163
2030745

HINT

對於 100%的資料有 1 ≤ Ki ≤ 10^9,  T ≤ 50

題目分析:題目要求第k個無平方因子數,我們顯然不可能把答案都求出來再查詢,這個資料範圍首先想到的是二分,對於第1-n的無平方因子數我們可以用容斥定理得到,拿總的個數減去4的倍數(-n/4個),減去9的倍數(-n/9個),但是36既是4的倍數又是9的倍數,被減了兩次,要加回來(+n/36),這樣容斥就出來了,前面的符號正好和數字開根號後對應的莫比烏斯函式相同,這樣問題就簡單了,還有一點要說明的是二分的上界開多大,這個也影響著莫比烏斯函式要開多大,我們不妨假設第k個無平方因子數不會超過2k,具體證明我也不會,但是最小的平方因子是4,也就是說每4個數裡必然有一個是平方因子數,同時因為平方因子越往後越大,可以yy出平均每四個數有不超過兩個平方因子數這個結論,所以第k個無平方因子數不會超過2k,(其實打表也可驗證),所以二分上界取2k+1即可,莫比烏斯函式開sqrt(2e9)差不多5e4的樣子

1000ms過

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int const MAX = 5e4;
ll const INF = 2e9;
int mob[MAX], p[MAX];
bool prime[MAX];

void Mobius()
{
    int pnum = 0;
    memset(prime, true, sizeof(prime));
    mob[1] = 1;
    for(int i = 2; i < MAX; i++)
    {
        if(prime[i])
        {
            p[pnum ++] = i;
            mob[i] = -1;
        }
        for(int j = 0; j < pnum && i * p[j] < MAX; j++)
        {
            prime[i * p[j]] = false;
            if(i % p[j] == 0)
            {
                mob[i * p[j]] = 0;
                break;
            }
            mob[i * p[j]] = -mob[i];
        }
    }
}

ll cal(int mid)
{   
    ll pos = 0;
    for(int i = 1; i * i <= mid; i++)
        pos += (ll) mob[i] * (mid / (i * i));
    return pos;
}

int main()
{
    Mobius();
    int T;
    scanf("%d", &T);
    while(T--)
    {
        ll k;
        scanf("%lld", &k);
        ll l = 1, r = 2 * k + 1;
        while(l <= r)
        {
            ll mid = (l + r) >> 1;
            if(cal(mid) < k)
                l = mid + 1;
            else
                r = mid - 1;
        }
        printf("%lld\n", l);
    }
}