HDU6987 Cycle Binary 反演,杜教筛

Cycle Binary – HDU 6987 – Virtual Judge (vjudge.net)

题意:对一个01串$s$,设$s$的权值是最大的$k$,使得$s$的某个前缀可以表示成一个相同的串重复$k$次,并且剩余的部分一定是循环节的一个前缀。

现在给定$n$,对所有长为$n$的01串,求其权值的和。


关于循环节的问题肯定需要先知道长为$n$的不循环的01串的个数。

实际上莫比乌斯反演一下就有

$$ f(n) = \sum_{d | n} \mu \left( \frac n d \right) 2 ^ d $$

考虑一个比较显然的性质,如果$k \ge 2$,那么选择循环节的方法是唯一的。(又叫周期定理)

那么对于$k$至少为$2$的部分,贡献就是

$$ \begin{aligned}
& \sum_{d = 1} ^ {\left\lfloor \frac n 2 \right\rfloor} f(d) \left\lfloor\frac n d\right\rfloor \\
= & \sum_{d = 1} ^ {\left\lfloor \frac n 2 \right\rfloor} \left\lfloor\frac n d\right\rfloor \sum_{k | d} \mu \left( \frac d k \right) 2 ^ k \\
= & \sum_{k = 1} ^ {\left\lfloor \frac n 2 \right\rfloor} 2 ^ k \sum_{d = 1} ^ {\left\lfloor \frac n k \right\rfloor} \mu(d) \left\lfloor \frac n {dk} \right\rfloor
\end{aligned} $$

后面的和式显然分块求和再套一个$\mu$的杜教筛就行了,那么外层也可以分块求和,写一个$f$的前缀和即可。

另外关于$k = 1$的部分,实际上它并不等于$f(n)$,例如abababa就是反例。

正确的计算方法是用$2^n$减去所有$k\ge 2$的方案数,也就是$2^n – \sum_{i = 1} ^ {\left\lfloor \frac n 2 \right\rfloor} f(i)$。这个东西之前一定计算过,直接拿来就好了。

为了优化复杂度,所有东西都预处理$n ^ {\frac 2 3}$以内的部分。

本题主要的难点大概在于$k = 1$时如何正确的统计方案数。

#include <bits/stdc++.h>

using namespace std;

constexpr int maxn = 2000005, p = 998244353;
constexpr int table_size = 1000000;

int qpow(int a, int b) {
	int ans = 1;

	while (b) {
		if (b & 1)
			ans = (long long)ans * a % p;
		
		b >>= 1;
		a = (long long)a * a % p;
	}

	return ans;
}

int pwa[100005], pwb[100005];
constexpr int base = 100000;

int getpw(int n) {
	return (long long)pwa[n % base] * pwb[n / base] % p;
}

bool notp[maxn];
int prime[maxn / 10 + 5], prime_cnt;
int mu[maxn], f[maxn], mu_tbl[maxn], f_tbl[maxn];
int smu[maxn], sf[maxn];

int N;

void get_table() {
	const int n = table_size;

	mu[1] = 1;
	// f[1] = 2;

	for (int i = 2; i <= n; i++) {
		if (!notp[i]) {
			prime[++prime_cnt] = i;
			mu[i] = -1;
		}

		for (int j = 1; j <= prime_cnt && i * prime[j] <= n; j++) {
			notp[i * prime[j]] = true;

			if (i % prime[j])
				mu[i * prime[j]] = -mu[i];
			else
				break;
		}
	}

	for (int i = 1, pw = 1; i <= n; i++) {
		pw = pw * 2 % p;

		for (int j = i, k = 1; j <= n; j += i, k++) {
			f[j] += mu[k] * pw;

			if (f[j] < 0)
				f[j] += p;
			else if (f[j] >= p)
				f[j] -= p;
		}
	}

	for (int i = 1; i <= n; i++) {
		mu_tbl[i] = mu_tbl[i - 1] + mu[i];
		if (mu_tbl[i] < 0)
			mu_tbl[i] += p;
		else if (mu_tbl[i] >= p)
			mu_tbl[i] -= p;
		
		f_tbl[i] = (f_tbl[i - 1] + f[i]) % p;
	}
}

int getmu(int n) {
	if (n <= table_size)
		return mu_tbl[n];
	else if (smu[N / n])
		return smu[N / n];
	
	int ans = 0;

	for (int i = 2, last; i <= n; i = last + 1) {
		last = n / (n / i);
		ans = (ans + (long long)(last - i + 1) * getmu(n / i)) % p;
	}

	return smu[N / n] = (p + 1 - ans) % p;
}

int getsum(int l, int r) {
	return (getpw(r + 1) - getpw(l + 1) + p) % p;
}

int getf(int n) {
	if (n <= table_size) {
		// printf("Getf(%d) = %d\n", n, f_tbl[n]);
		return f_tbl[n];
	}
	else if (sf[N / n])
		return sf[N / n];
	
	int ans = 0;

	for (int i = 1, last; i <= n; i = last + 1) {
		last = n / (n / i);
		ans = (ans + (long long)getsum(i - 1, last) * getmu(n / i)) % p;
	}

	return sf[N / n] = ans;
}

int main() {
	pwa[0] = pwb[0] = 1;
	// int tpw = 1;

	for (int i = 1; i <= base; i++)
		pwa[i] = pwa[i - 1] * 2 % p;
	
	for (int i = 1; i < base; i++)
		pwb[i] = (long long)pwb[i - 1] * pwa[base] % p;
	
	get_table();

	ios::sync_with_stdio(false);

	/*
	for (int i = 1; i <= 10; i++)
		cout << "f[" << i << "] = " << f[i] << endl;
*/
	int T;
	cin >> T;

	while (T--) {
		cin >> N;

		int ans = (getpw(N) - getf(N / 2) + p) % p;

		// cerr << "ans = " << ans << "\n";

		for (int i = 1, last; i <= N / 2; i = last + 1) {
			last = N / (N / i);
			ans = (ans + (long long)(getf(last) - getf(i - 1) + p) * (N / i)) % p;
		}

		cout << ans << "\n";

		memset(sf, 0, sizeof(sf));
		memset(smu, 0, sizeof(smu));
	}

	return 0;
}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注