[BZOJ4652/UOJ#221][NOI2016]循环之美(莫比乌斯反演+杜教筛)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=4652
http://uoj.ac/problem/221

Solution……

一、 O(nm) O ( n m )

我们知道, k k 进制小数 0.0000000...10000000..10000000..1... (循环节为 x1 x − 1 0 0 1 1 1 )等于 1kx1
于是,乱搞一下 gcd(x,y)=1 gcd ( x , y ) = 1 xy x y 纯循环当且仅当存在一个 z>0 z > 0 满足 y|kz1 y | k z − 1
于是:

kz1(mody) k z ≡ 1 ( mod y )

如果 gcd(k,y)1 gcd ( k , y ) ≠ 1 则易得上面的方程无解。否则根据欧拉定理,一个合法解是 z=ϕ(y) z = ϕ ( y )
于是,我们要求的答案为:
i=1nj=1m[gcd(i,j)=1][gcd(j,k)=1] ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = 1 ] [ gcd ( j , k ) = 1 ]

二、反演

上式 = =

j=1m[gcd(j,k)=1]i=1n[gcd(i,j)=1]

=j=1m[gcd(j,k)=1]i=1nd|gcd(i,j)μ(d) = ∑ j = 1 m [ gcd ( j , k ) = 1 ] ∑ i = 1 n ∑ d | gcd ( i , j ) μ ( d )

=dμ(d)ndj=1,d|jm[gcd(j,k)=1] = ∑ d μ ( d ) ⌊ n d ⌋ ∑ j = 1 , d | j m [ gcd ( j , k ) = 1 ]

=dμ(d)ndj=1md[gcd(jd,k)=1] = ∑ d μ ( d ) ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ( j d , k ) = 1 ]

=d=1min(n,m)[gcd(d,k)=1]μ(d)ndj=1md[gcd(j,k)=1] = ∑ d = 1 min ( n , m ) [ gcd ( d , k ) = 1 ] μ ( d ) ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ( j , k ) = 1 ]

d d 下界分块即可。
可以考虑求:
f(n)=i=1n[gcd(i,k)=1]

使得该式变为:
ans=d=1min(n,m)[gcd(d,k)=1]μ(d)ndf(md) a n s = ∑ d = 1 min ( n , m ) [ gcd ( d , k ) = 1 ] μ ( d ) ⌊ n d ⌋ f ( ⌊ m d ⌋ )

由于 nd ⌊ n d ⌋ 的取值只有 O(n) O ( n ) 种, md ⌊ m d ⌋ 的取值只有 O(m) O ( m ) 种,故可对 d d 下界分块,需要求出 [gcd(d,k)=1]μ(d) 的前缀和:
g(n,k)=i=1n[gcd(i,k)=1]μ(i) g ( n , k ) = ∑ i = 1 n [ gcd ( i , k ) = 1 ] μ ( i )

三、解决掉 f f

我们知道,gcd(a,b)=gcd(a+k×b,b)
于是:

f(n)=nkϕ(k)+i=1nmodk[gcd(i,k)=1] f ( n ) = ⌊ n k ⌋ ϕ ( k ) + ∑ i = 1 n mod k [ gcd ( i , k ) = 1 ]

预处理出 cnt[i] c n t [ i ] 表示 [1,i] [ 1 , i ] 中与 k k 互质的数的个数之后,就可以 O(1) f f

四、解决掉 g

考虑再反演一次:

g(n,k)=d|kμ(d)i=1,d|inμ(i) g ( n , k ) = ∑ d | k μ ( d ) ∑ i = 1 , d | i n μ ( i )

=d|kμ(d)i=1ndμ(id) = ∑ d | k μ ( d ) ∑ i = 1 ⌊ n d ⌋ μ ( i d )

众所周知, μ μ 是个积性函数,且有:
μ(ab)={mu(a)×mu(b)0gcd(a,b)=1gcd(a,b)1 μ ( a b ) = { m u ( a ) × m u ( b ) gcd ( a , b ) = 1 0 gcd ( a , b ) ≠ 1

故:
g(n,k)=d|kμ(d)2i=1nd[gcd(i,d)=1]μ(i)=d|kμ(d)2g(nd,d) g ( n , k ) = ∑ d | k μ ( d ) 2 ∑ i = 1 ⌊ n d ⌋ [ gcd ( i , d ) = 1 ] μ ( i ) = ∑ d | k μ ( d ) 2 g ( ⌊ n d ⌋ , d )

我们得到 g(n,k) g ( n , k ) 的递推式,可用记忆化搜索求得。
特别地,由于任意都与 1 1 互质,故 g(n,1)=i=1nμ(i) 。故当 k=1 k = 1 时杜教筛即可。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define Edge(u) for (int e = adj[u]; e; e = nxt[e])
#define For(i, a, b) for (i = a; i <= b; i++)
#define Step(i, a, b, x) for (i = a; i <= b; i += x)
#define Mobius(i, a, b) for (i = a; i <= b;)
using namespace std;

typedef long long ll;
const int MaxN = 1e6, N = MaxN + 5, MaxM = 2000, M = MaxM + 5, E = 41,
ZZQ = 19260817, L = 2e7 + 5, INF = 0x3f3f3f3f;
int n, m, k, tot, pri[N], miu[N], sum[N], divis[M][M], ums[M],
ecnt, nxt[L], adj[ZZQ], gon[L], gok[L], val[L];
bool mark[N];
ll ans;

template <class T>
T Min(T a, T b) {return a < b ? a : b;}

void ins(int n, int x, int f)
{
    int u = (1ll * (n - 1) * k + x) % ZZQ;
    nxt[++ecnt] = adj[u]; adj[u] = ecnt;
    gon[ecnt] = n; gok[ecnt] = x; val[ecnt] = f;
}

int query(int n, int x)
{
    int u = (1ll * (n - 1) * k + x) % ZZQ;
    Edge(u) if (gon[e] == n && gok[e] == x)
        return val[e];
    return INF;
}

void sieve()
{
    int i, j;
    mark[0] = mark[miu[1] = 1] = 1;
    For (i, 2, MaxN)
    {
        if (!mark[i]) miu[pri[++tot] = i] = -1;
        For (j, 1, tot)
        {
            if (1ll * i * pri[j] > MaxN) break;
            mark[i * pri[j]] = 1;
            if (i % pri[j] == 0) break;
            else miu[i * pri[j]] = -miu[i];
        }
    }
    For (i, 1, MaxN) sum[i] = sum[i - 1] + miu[i];
    For (i, 1, MaxM) Step (j, i, MaxM, i)
        divis[j][++divis[j][0]] = i;
    For (i, 1, MaxM) ums[i] = ums[i - 1] + (__gcd(i, k) == 1);
}

int f(int n)
{
    return (n / k) * ums[k] + ums[n % k];
}

int mmp;

int S(int n, int k)
{
    int i, res;
    if (n <= 1) return ins(n, k, n), n;
    if ((res = query(n, k)) < INF) return res;
    res = 0;
    if (k == 1)
    {
        if (n <= MaxN) return sum[n];
        res = 1;
        Mobius (i, 2, n)
        {
            int nxt = n / (n / i);
            res -= (nxt - i + 1) * S(n / i, k);
            i = nxt + 1;
        }
        return ins(n, k, res), res;
    }
    For (i, 1, divis[k][0])
    {
        int x = divis[k][i];
        if (miu[x] != 0) res += S(n / x, x);
    }
    return ins(n, k, res), res;
}

int main()
{
    int i;
    cin >> n >> m >> k;
    sieve();
    Mobius (i, 1, Min(n, m))
    {
        int nxt = Min(n / (n / i), m / (m / i));
        ans += 1ll * (n / i) * f(m / i) * (S(nxt, k) - S(i - 1, k));
        i = nxt + 1;
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值