【loj - 2085】「NOI2016」循环之美


description

牛牛是一个热爱算法设计的高中生。在他设计的算法中,常常会使用带小数的数进行计算。牛牛认为,如果在 \(k\) 进制下,一个数的小数部分是纯循环的,那么它就是美的。

现在,牛牛想知道:对于已知的十进制数 \(n\)\(m\),在 \(k\) 进制下,有多少个数值上互不相等的纯循环小数,可以用分数 \(\frac{x}{y}\) 表示,其中 \(1\leq x \leq n, 1 \leq y \leq m\),且 \(x, y\) 是整数。

一个数是纯循环的,当且仅当其可以写成以下形式:

\[a.\dot{c_1}c_2c_3\dots c_{p-1}\dot{c_{p}} \]

其中,\(a\) 是一个整数,\(p\geq 1\);对于 \(1\leq i \leq p\)\(c_i\)\(k\) 进制下的一位数字。

例如,在十进制下,\(0.45454545\cdots=0.\dot{4}\dot{5}\) 是纯循环的,它可以用 \(\frac{5}{11}\)\(\frac{10}{22}\) 等分数表示;在十进制下,\(0.1666666\cdots=0.1\dot{6}\) 则不是纯循环的,它可以用 \(\frac{1}{6}\) 等分数表示。

需要特别注意的是,我们认为一个整数是纯循环的,因为它的小数部分可以表示成 \(0\) 的循环或是 \(k-1\) 的循环;而一个小数部分非 \(0\) 的有限小数不是纯循环的。

solution

考虑既约分数 \(\frac{j}{i}\) 是纯循环的等价条件:存在 \(p\) 使得 \(j\times k^p=j \mod i\)

由于 \(j, i\) 互质,那么上述条件等价于 \(k, i\) 也互质。因此答案为:

\[\sum_{i=1}^{m}\sum_{j=1}^{n}[\gcd(i,k) = 1][\gcd(i,j)=1] \]

首先套路反演:

\[\sum_{d|k}\mu(d)\sum_{p}\mu(p)\times\lfloor\frac{n}{p}\rfloor\times\lfloor\frac{m}{{\rm lcm}(d,p)}\rfloor \]

二度反演:

\[\sum_{a|b|d|k}\mu(d)\sum_{p'}\mu(b\times p')\times\lfloor\frac{n}{b\times p'}\rfloor\times\lfloor\frac{m\times a}{d\times b\times p'}\rfloor\times\mu(b / a) \]

枚举四元组 \((a, b, d, k)\),记 \(n' = \lfloor\frac{n}{b}\rfloor, m'=\lfloor\frac{m\times a}{d\times b}\rfloor\),则我们相当于求:

\[\sum_{p'}\mu(b\times p')\times\lfloor\frac{n'}{p'}\rfloor\times\lfloor\frac{m'}{p'}\rfloor \]

后面部分很明显可以分块。考虑怎么快速求 \(S_{b}(n) = \sum_{i=1}^{n}\mu(b\times i)\),三度反演:

\[\begin{aligned} \sum_{i=1}^{n}\mu(b\times i) &=\sum_{i=1}^{n}\mu(b)\times\mu(i)\times[\gcd(b,i)=1] \\ &=\sum_{d|b}\mu(b)\times\mu(d)\times S_{d}(\lfloor\frac{n}{d}\rfloor) \end{aligned} \]

\(b = 1\) 时直接杜教筛,否则就做如上的过程。不难发现 \(b\not=1\) 时转移无环。

对于最后一步 \(b\not = 1\) 的时间复杂度分析:一共会有 \(O(d(k)\times\sqrt{n})\) 个状态,每个状态有 \(O(d(k))\) 种转移。所以可以通过。

accepted code

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXK = 2000;
const int MAXN = 1000000;
const ll INF = ll(1E12);

int mu[MAXN + 5], prm[MAXN + 5], pcnt;
bool nprm[MAXN + 5];

vector<int>dv[MAXK + 5];
void init() {
	for(int i=1;i<=MAXK;i++)
		for(int j=i;j<=MAXK;j+=i)
			dv[j].push_back(i);
			
	mu[1] = 1;
	for(int i=2;i<=MAXN;i++) {
		if( !nprm[i] ) prm[++pcnt] = i, mu[i] = -1;
		for(int j=1;i*prm[j]<=MAXN;j++) {
			nprm[i*prm[j]] = true;
			if( i % prm[j] == 0 ) {
				mu[i*prm[j]] = 0;
				break;
			}
			else mu[i*prm[j]] = -mu[i];
		}
	}
}

ll f[20][MAXN + 5]; int id1[MAXN + 5], id2[MAXN + 5], tot;
int id(int x) {return (x <= MAXN ? id1[x] : id2[INF / x]);}

ll smu[20][MAXN + 5]; int A[MAXK + 5], B[20], cnt;
ll sum(int x, int n) {
	ll &ans = f[x][id(n)];
	if( ans != INF ) return ans;
	if( n <= MAXN ) return ans = smu[x][n];
	
	if( x == 1 ) {
		ans = 1;
		for(int i=2,j;i<=n;i=j+1) {
			int p = (n / i); j = n / p;
			ans -= (j - i + 1)*sum(x, p);
		}
	} else {
		ans = 0;
		for(unsigned i=0;i<dv[B[x]].size();i++) {
			int d = dv[B[x]][i];
			ans += mu[d]*sum(A[d], n / d);
		}
		ans *= mu[B[x]];
	}
	return ans;
}

void get_id(int n, int m) {
	tot = 0;
	for(int i=1;i<=n&&i<=m;i++) {
		i = min(n / (n / i), m / (m / i));
		if( i <= MAXN ) id1[i] = (++tot);
		else id2[INF / i] = (++tot);
		
		for(int j=1;j<=cnt;j++) f[j][tot] = INF;
	}
}
ll solve(int b, int n, int m) {
	get_id(n, m); ll ret = 0;
	for(int i=1,j;i<=n&&i<=m;i=j+1) {
		int p = (n / i), q = (m / i);
		j = min(n / p, m / q);
		ret += 1LL*(sum(A[b], j) - sum(A[b], i - 1))*p*q;
	}
	return ret;
/*
	ll ret = 0;
	for(int p=1;p<=n;p++)
		ret += 1LL*mu[p*b]*(n / p)*(m / p);
	return ret;
*/
}

bool tag[MAXN + 5];
void get(int k) {
	for(unsigned i=0;i<dv[k].size();i++)
		if( mu[dv[k][i]] ) B[++cnt] = dv[k][i], A[dv[k][i]] = cnt;
	for(int i=1;i<=cnt;i++) {
		tag[1] = true, smu[i][1] = 1;
		for(int j=2;j<=MAXN;j++) {
			if( !nprm[j] ) tag[j] = (B[i] % j);
			smu[i][j] = smu[i][j - 1] + (tag[j] ? mu[j] : 0);
			for(int p=1;j*prm[p]<=MAXN;p++) {
				nprm[j*prm[p]] = true, tag[j*prm[p]] = (tag[j] && tag[prm[p]]);
				if( j % prm[p] == 0 ) break;
			}
		}
		for(int j=1;j<=MAXN;j++) smu[i][j] *= mu[B[i]];
	}
}
int main() {
	int n, m, k;
	scanf("%d%d%d", &n, &m, &k);
	init(), get(k);
	
	ll ans = 0;
	for(unsigned i1=0;i1<dv[k].size();i1++) {
		int d = dv[k][i1]; if( !mu[d] ) continue;
		for(unsigned i2=0;i2<dv[d].size();i2++) {
			int b = dv[d][i2];
			for(unsigned i3=0;i3<dv[b].size();i3++) {
				int a = dv[b][i3];
				
				ans += mu[d] * mu[b / a] * solve(b, n / b, 1LL * m * a / b / d);
			}
		}
	}
	printf("%lld\n", ans);
}

details

虽然 \(\sum\mu\) 不会溢出,但中间过程可能会溢出,所以还是要开 long long。

posted @ 2020-06-16 11:13  Tiw_Air_OAO  阅读(182)  评论(0编辑  收藏  举报