1. 程式人生 > >Bzoj4652: [Noi2016]循環之美

Bzoj4652: [Noi2016]循環之美

lld esp 進制 down turn eve 不為 line pos

題面

傳送門

Sol

\(設x,y且gcd(x, y)=1\)若使\(\frac{x}{y}\)\(k\)進制小數是純循環小數
則一定存在某次除法中余數在之前出現過
也就是存在\(L>0\)\(x\equiv x*k^L(mod\ y)\)
\(x,y互質\)那麽同時乘上x的逆元則\(k^L\equiv1(mod\ y)\)
所以\(k^L=a*y+1(a \in Z)\)
\(y\)\(a*y+1\)互質,輾轉相除法可證
所以\(k^L與y\)互質,易得\(k與y也互質\)

所以我們就證明了偽證\(k與y\)互質時,才會出現\(\frac{x}{y}\)\(k\)進制小數是純循環小數

接下來就解題辣
暴力求就不講了
直接推柿子:
要求的就是\(\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i, j)==1, gcd(j, k)==1]\)
\[=\sum_{i=1}^{m}[gcd(i, k)==1]\sum_{j=1}^{n}[gcd(i, j)==1]\]
莫比烏斯反演一波
\[=\sum_{i=1}^{m}[gcd(i, k)==1]\sum_{j=1}^{n}\sum_{d|i,d|j}\mu(d)\]
\[=\sum_{i=1}^{m}[gcd(i, k)==1]\sum_{d|i}^n\mu(d)\lfloor\frac{n}{d}\rfloor\]


\[=\sum_{d=1}^{n}\mu(d)\lfloor\frac{n}{d}\rfloor\sum_{d|i}[gcd(i, k)==1]\]
\[=\sum_{d=1}^{n}\mu(d)\lfloor\frac{n}{d}\rfloor[gcd(d, k)==1]\sum_{i=1}^{\lfloor\frac{m}{d}\rfloor}[gcd(i, k)==1]\]

如果\(\sum_{i=1}^{\lfloor\frac{m}{d}\rfloor}[gcd(i, k)==1]\)可以預處理\(O(1)\)
那麽就有了\(O(n)\)的解法

\(x=\lfloor\frac{m}{d}\rfloor\)


如果\(x<=k\)直接\(O(k)\)處理
否則
因為\(gcd(i, k)=gcd(i\%k, k)\),所以\(O(k)\)處理後乘上\(\lfloor\frac{x}{k}\rfloor\)

$O(n)$84分常數很大

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int _(2e7 + 1);

IL ll Read(){
    RG ll x = 0, z = 1; RG char c = getchar();
    for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

int N, num, prime[_], mu[_], k, f[2010];
bool isprime[_];

IL int Gcd(RG int x, RG int y){  return !y ? x : Gcd(y, x % y);  }

IL void Prepare(){
    isprime[1] = 1; mu[1] = 1;
    for(RG int i = 2; i < N; ++i){
        if(!isprime[i]) prime[++num] = i, mu[i] = -1;
        for(RG int j = 1; j <= num && i * prime[j] < N; ++j){
            isprime[i * prime[j]] = 1;
            if(i % prime[j]) mu[i * prime[j]] = -mu[i];
            else{  mu[i * prime[j]] = 0; break;  }
        }
    }
    for(RG int i = 1; i <= k; ++i) f[i] = f[i - 1] + (Gcd(i, k) == 1);
}

int main(RG int argc, RG char* argv[]){
    RG int n = Read(), m = Read(); k = Read();
    N = min(m + 1, _); Prepare(); RG ll ans = 0;
    for(RG int i = 1; i <= n; ++i){
        if(Gcd(i, k) != 1) continue;
        RG ll x = m / i, F = f[x % k] + 1LL * (x / k) * f[k];
        ans += 1LL * mu[i] * (n / i) * F;
    }
    printf("%lld\n", ans);
    return 0;
}

怎麽優化?
\[\sum_{d=1}^{n}\mu(d)\lfloor\frac{n}{d}\rfloor[gcd(d, k)==1]\sum_{i=1}^{\lfloor\frac{m}{d}\rfloor}[gcd(i, k)==1]\]
這玩意兒後面都可以\(O(1)\)求了
又看到\(\lfloor\frac{n}{d}\rfloor和\lfloor\frac{m}{d}\rfloor\)可以分塊
如果能求出\(\mu(d)[gcd(d, k)==1]\)的前綴和就好了

那就繼續推柿子:
\[G(n,k)=\sum_{d=1}^{n}\mu(d)[gcd(d, k)==1]\]
後面的\(gcd\)和之前推的時候類似,這裏是一樣的,所以莫比烏斯反演一波得
\[\sum_{d=1}^{n}\mu(d)\sum_{i|d, i|k}\mu(i)\]
\[=\sum_{i|k}^{k}\mu(i)\sum_{i|d}^{n}\mu(d)\]
\[=\sum_{i|k}^{k}\mu(i)\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}\mu(i*j)\]
後面的\(\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}\mu(i*j)\),顯然只有當\(gcd(i, j)=1\)\(\mu(i*j)\)才不為\(0\)
所以可以寫成\(\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}\mu(i)\mu(j)[gcd(i,j)==1]\)
代入
\[=\sum_{i|k}^{k}\mu(i)\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}\mu(i)\mu(j)[gcd(i,j)==1]\]
\[=\sum_{i|k}^{k}\mu^2(i)\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}\mu(j)[gcd(i,j)==1]\]
\[=\sum_{i|k}^{k}\mu^2(i)G(\lfloor\frac{n}{i}\rfloor,i)\]
前面的也可以\(O(k)\)處理出來,\(G\)貌似可以杜教篩似的遞歸處理
暴力在篩裏搞一搞k的約數
那不就做完了,復雜度不會證明\(O(跑的過)\)

某些OJ的數據太強啦要卡一卡預處理的大小才能過

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int _(4e6 + 1);

IL ll Read(){
    RG ll x = 0, z = 1; RG char c = getchar();
    for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

int N, num, prime[_], mu[_], k, f[2010], smu[_];
map < pair <int, int> , ll > G;
bool isprime[_];

IL int Gcd(RG int x, RG int y){  return !y ? x : Gcd(y, x % y);  }

IL void Sieve(){
    isprime[1] = 1; smu[1] = mu[1] = 1;
    for(RG int i = 2; i < N; ++i){
        if(!isprime[i]) prime[++num] = i, mu[i] = -1;
        for(RG int j = 1; j <= num && i * prime[j] < N; ++j){
            isprime[i * prime[j]] = 1;
            if(i % prime[j]) mu[i * prime[j]] = -mu[i];
            else{  mu[i * prime[j]] = 0; break;  }
        }
        smu[i] = smu[i - 1] + mu[i];
    }
    for(RG int i = 1; i <= k; ++i) f[i] = f[i - 1] + (Gcd(i, k) == 1);
}

IL ll Du_Sieve(RG int n, RG int m){
    if(!n) return 0;
    if(m == 1 && n < _) return smu[n];
    if(G[make_pair(n, m)]) return G[make_pair(n, m)];
    RG ll ans = 0;
    if(m == 1){
        ans = 1;
        for(RG int i = 2, j; i <= n; i = j + 1){
            j = n / (n / i);
            ans -= Du_Sieve(n / i, 1) * (j - i + 1);
        }
    }
    else{
        for(RG int i = 1; i * i <= m; ++i){
            if(m % i) continue;
            if(mu[i] != 0) ans += Du_Sieve(n / i, i);
            if(i * i != m && mu[m / i] != 0) ans += Du_Sieve(n / (m / i), m / i);
        }
    }
    return G[make_pair(n, m)] = ans;
}

int main(RG int argc, RG char* argv[]){
    RG int n = Read(), m = Read(); k = Read();
    N = min(m + 1, _); Sieve(); RG ll ans = 0, nxt, lst = 0;
    for(RG int i = 1, j; i <= n; i = j + 1){
        j = n / (n / i); if(m / i) j = min(j, m / (m / i));
        RG ll x = m / i, F = f[x % k] + 1LL * (x / k) * f[k];
        nxt = Du_Sieve(j, k);
        ans += 1LL * (nxt - lst) * (n / i) * F;
        lst = nxt;
    }
    printf("%lld\n", ans);
    return 0;
}

柿子比較多,如有錯誤請指正

Bzoj4652: [Noi2016]循環之美