【hdu 6987】Cycle Binary (杜教筛)

题目链接

题目大意

我们可以将字符串 s s s 写成 k p + p ′ kp+p' kp+p 的形式(其中 p ′ p' p p p p 的前缀),表示将字符串 p p p 重复 k k k 次后拼接上 p ′ p' p 。定义 s s s循环次数 v ( s ) v(s) v(s) k k k 的最大值。
对所有长度为 n ( ≤ 1 0 9 ) n(\le 10^9) n(109) 的 01 串 s s s ,求他们的 v ( s ) v(s) v(s) 之和。

思路

f ( i ) f(i) f(i) 为长度为 n n n 的,最短循环节长度恰好为 i i i 的 01 串个数,则
2 x = ∑ d ∣ x f ( d ) 2^x=\sum_{d\mid x}f(d) 2x=dxf(d)

2 x = f ∗ I 2^x=f*I 2x=fI

f = 2 x ∗ μ f=2^x*\mu f=2xμ
其中 ∗ * 为 Dirichlet卷积, I I I 为全1函数, μ \mu μ 为Mobius函数。
(注意 x ≤ n 2 x\le \frac{n}{2} x2n 时成立, x > n 2 x>\frac{n}{2} x>2n 时不一定成立,如10101循环节可以是2, 4, 5)
而答案
a n s = ∑ i = 1 n [ n i ] f ( i ) ans=\sum_{i=1}^n[\frac{n}{i}]f(i) ans=i=1n[in]f(i)
因此,我们只需快速求解 f ( x ) f(x) f(x) 的前缀和,然后利用数论分块即可解决该题。
可以利用杜教筛解决。

杜教筛

参考
S f ∗ g ( n ) = ∑ i = 1 n ∑ d ∣ i g ( d ) f ( i d ) = ∑ d = 1 n g ( d ) S f ( [ n d ] ) S_{f*g}(n)=\sum_{i=1}^n\sum_{d\mid i}g(d)f(\frac{i}{d})=\sum_{d=1}^ng(d)S_f([\frac{n}{d}]) Sfg(n)=i=1ndig(d)f(di)=d=1ng(d)Sf([dn])

S f ( n ) = S f ∗ g ( n ) − ∑ d = 2 n g ( d ) S f ( [ n d ] ) S_f(n)=S_{f*g}(n)-\sum_{d=2}^ng(d)S_f([\frac{n}{d}]) Sf(n)=Sfg(n)d=2ng(d)Sf([dn])
可以先求出 n 2 3 n^\frac{2}{3} n32 S f ( n ) S_f(n) Sf(n) ,对大范围的 S f ( n ) S_f(n) Sf(n) 用 map 存一下。
S f ∗ g ( n ) S_{f*g}(n) Sfg(n) 可以 O ( 1 ) O(1) O(1) 求解,则总复杂度为 O ( n 2 3 ) O(n^\frac{2}{3}) O(n32)

回到本题, S f ( n ) = ( 2 n + 1 − 2 ) − ∑ d = 2 n S f ( [ n d ] ) S_f(n)=(2^{n+1}-2)-\sum_{d=2}^nS_f([\frac{n}{d}]) Sf(n)=(2n+12)d=2nSf([dn])

但我们只能求 S f ( d ) ( d ≤ n 2 ) S_f(d) (d\le \frac{n}{2}) Sf(d)(d2n) ,剩下的用 2 n 2^n 2n 减去即可。

代码

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define per(i, r, l) for (int i = r; i >= l; --i)
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1000006;
int pw(int x, int y) {
    int ret = 1;
    while (y) {
        if (y & 1) ret = 1ll * ret * x % mod;
        x = 1ll * x * x % mod;
        y >>= 1;
    }
    return ret;
}
int prime[N], tot = 0;
bool iscmp[N];
int mu[N];
int f[N], ss[N];
void init() {
    mu[1] = 1;
    rep(i, 2, 1000000) {
        if (!iscmp[i]) prime[++tot] = i, mu[i] = mod - 1;
        for (int j = 1; j <= tot && i * prime[j] <= 1000000; ++j) {
            iscmp[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            } else {
                mu[i * prime[j]] = mod - mu[i];
            }
        }
    }
    int t = 1;
    rep(i, 1, 1000000) {
        t = t * 2 % mod;
        for (int j = 1; i * j <= 1000000; ++j) {
            f[i * j] = (f[i * j] + 1ll * t * mu[j]) % mod;
        }
    }
    rep(i, 1, 1000000) { ss[i] = (ss[i - 1] + f[i]) % mod; }
}

map<int, int> s;
int sum(int n) {
    if (n <= 1000000) return ss[n];
    if (s.count(n)) return s[n];
    int ret = (pw(2, n + 1) - 2 + mod) % mod;
    for (int i = 2, last; i <= n; i = last + 1) {
        last = n / (n / i);
        ret = (ret - 1ll * sum(n / i) * (last - i + 1) % mod + mod) % mod;
    }
    return s[n] = ret;
}
int solve(int n) {
    int ret = 0;
    for (int i = 1, last; i <= n / 2; i = last + 1) {
        last = n / (n / i);
        ret = (ret + 1ll * (sum(last) - sum(i - 1) + mod) * (n / i)) % mod;
    }
    ret = (1ll * ret + pw(2, n) - sum(n / 2) + mod) % mod;
    return ret;
}
int T;
int n;

int main() {
    init();
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        printf("%d\n", solve(n));
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值