【UOJ #221】【NOI 2016】循环之美

http://uoj.ac/problem/221
因为\(a\)\(b\)不互质时,\(\frac ab=\frac{\frac a{(a,b)}}{\frac b{(a,b)}}\),所以只用求\(a\)\(b\)互质时的满足条件的个数。
\(\frac ab\)\(k\)进制下是纯循环小数,我们先假设循环节长度为\(l\),这样\(\frac ab\times k^l-\frac ab\)的小数部分就是0,也就是这是个整数。
\(\frac{a\left(k^l-1\right)}b\)是个整数,就是说\(b|a\left(k^l-1\right)\)。又因为\(a\bot b\),所以\(b|\left(k^l-1\right)\)
判断\(\frac ab\)是否是\(k\)进制下的纯循环小数就转变成了判断是否存在一个\(l,l\geq 0\),满足\(\left(k^l-1\right)\mod b=0\)
\(k\)\(b\)不互质时,对于所有的\(l\)\(b\)\(k^l\)有共同的质因子,又因为\(k^l\bot\left(k^l-1\right)\),所以\(b\)\(k^l\)的共同的质因子是\(k^l-1\)没有的,所以不存在\(l\)满足\(b|\left(k^l-1\right)\)
\(k\)\(b\)互质时,由欧拉定理:\(k^{\varphi(b)}\mod b=1\),存在\(l=\varphi(b)\)
这样答案就变成了:\[\sum_{a=1}^n\sum_{b=1}^m[b\bot k][a\bot b]\]
化一波式子:
\[ \begin{aligned} &\sum_{a=1}^n\sum_{b=1}^m[b\bot k][a\bot b]\\ =&\sum_{d=1}^n\mu(d)\sum_{a=1}^{\left\lfloor\frac nd\right\rfloor}\sum_{b=1}^{\left\lfloor\frac md\right\rfloor}[bd\bot k]\\ =&\sum_{d=1}^n\mu(d)[d\bot k]\left\lfloor\frac nd\right\rfloor\sum_{b=1}^{\left\lfloor\frac md\right\rfloor}[b\bot k] \end{aligned} \]
\(\sum\limits_{i=1}^n[i\bot k]\)很好预处理后\(O(1)\)计算。
重点是怎么算\(\sum\limits_{d=1}^n\mu(d)[d\bot k]\)
先放宽限制,把\([d\bot k]\)的限制去掉,这样就是对\(O\left(\sqrt n+\sqrt m\right)\)个下取整取值求\(\mu\)的前缀和,可以先\(O\left(n^{\frac 23}\right)\)大力杜教筛一波。
然后考虑\(S(i,n)\)表示\(1\sim n\)中与\(k\)的前\(i\)个质因子互质的数的\(\mu\)值和,这样\(S(i,n)=S(i-1,n)-\mu\left(p_i\right)S\left(i,\left\lfloor\frac n{p_i}\right\rfloor\right)\)递推求出,时间复杂度\(O\left(\left(\sqrt n+\sqrt m\right)\log k\right)\)(假设\(k\)的不同质因子有\(\log k\)个)。
最后对\(O\left(\sqrt n+\sqrt m\right)\)个不同的\(\left\lfloor\frac nd\right\rfloor\)\(\left\lfloor\frac md\right\rfloor\)的取值进行分段求和即可。
时间复杂度\(O\left(n^{\frac 23}+\left(\sqrt n+\sqrt m\right)\log k\right)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int N = 1000003;

bool notp[N];
int mu[N], sum_mu[N], prime[N], Num = 0;

void Euler_shai() {
    mu[1] = sum_mu[1] = 1;
    for (int i = 2; i <= 1000000; ++i) {
        if (!notp[i]) prime[++Num] = i, mu[i] = -1;
        for (int j = 1; j <= Num && prime[j] * i <= 1000000; ++j) {
            notp[prime[j] * i] = true;
            if (i % prime[j] == 0) break;
            mu[prime[j] * i] = -mu[i];
        }
        sum_mu[i] = sum_mu[i - 1] + mu[i];
    }
}

int R[N], cnt = 0, sum_mu2[N], n, m, k, S[N], pr[N], prnum = 0;

const int mo = 2333333;

struct HashTable {
    int pos[mo], num[mo];
    
    void ins(int nu, int po) {
        int tmp = nu % mo;
        while (num[tmp]) {++tmp; if (tmp == mo) tmp = 0;}
        num[tmp] = nu;
        pos[tmp] = po;
    }
    
    int query(int nu) {
        int tmp = nu % mo;
        while (num[tmp] != nu) {++tmp; if (tmp == mo) tmp = 0;}
        return pos[tmp];
    }
} HT;

int sum[N], id[N];

int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}

void pre_sum() {
    for (int i = 1; i <= k; ++i) {
        sum[i] = sum[i - 1];
        if (gcd(i, k) == 1)
            ++sum[i];
    }
}

int cal(int num) {return sum[k] * (num / k) + sum[num % k];}

int Sum(int num) {return num <= 1000000 ? S[id[num]] : S[HT.query(num)];}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    Euler_shai();
    
    for (int i = 1, j = 1; i <= n && i <= m; ++i) {
        j = m / (m / i);
        i = n / (n / i);
        if (i > j) i = j;
        R[++cnt] = i;
        if (i > 1000000) HT.ins(i, cnt);
    }
    
    for (int i = 1; i <= cnt; ++i) {
        int num = R[i], &ret = S[i];
        if (num <= 1000000) {ret = sum_mu[num]; id[num] = i; continue;}
        ret = 1;
        for (int j = 2, pre = 1; j <= num; pre = j, ++j) {
            j = num / (num / j);
            ret -= 1ll * Sum(num / j) * (j - pre);
        }
    }
    
    for (int i = 1; i <= Num && prime[i] <= k; ++i)
        if (k % prime[i] == 0)
            pr[++prnum] = prime[i];
    
    for (int i = 1, pi = pr[1]; i <= prnum; pi = pr[++i])
        for (int j = 1; j <= cnt; ++j)
            S[j] += Sum(R[j] / pi);
    
    pre_sum();
    
    ll ans = 0;
    for (int tmp = 1, i = 1, j = 1; i <= n && i <= m; ++tmp, ++i) {
        j = m / (m / i);
        i = n / (n / i);
        if (i > j) i = j;
        ans += 1ll * (S[tmp] - S[tmp - 1]) * (n / i) * cal(m / i);
    }
    
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/abclzr/p/6783268.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值