[NOI2016]循环之美

13 篇文章 0 订阅
12 篇文章 0 订阅

题目

传送门 to luogu

思路

纯循环小数 a a a,设循环节长度为 L L L,那么 k L a k^La kLa a a a 的小数部分相同,作差有 ( k L − 1 ) a ∈ Z (k^L{\rm-}1)a\in\Z (kL1)aZ,所以 a = v k L − 1    ( v ∈ Z ) a=\frac{v}{k^L-1}\;(v\in\Z) a=kL1v(vZ) 。另一方面,若 a a a 满足 ( k L − 1 ) a ∈ Z (k^L{\rm-}1)a\in\Z (kL1)aZ,那么 a a a 一定是纯循环小数,因为小数部分必然需要有长度为 L L L 的循环节(虽然可能不是最短)。

所以 a a a 是纯循环小数,当且仅当 a = v k L − 1    ( v ∈ Z ) a=\frac{v}{k^L-1}\;(v\in Z) a=kL1v(vZ) 。哪怕 gcd ⁡ ( v , k L − 1 ) ≠ 1 \gcd(v,k^L{\rm-}1)\ne 1 gcd(v,kL1)=1 也并不影响这一点。于是答案就是
a n s = ∑ i = 1 n ∑ j = 1 m [ ∃ L , x ,    s.t. i j = x k L − 1 ] [ gcd ⁡ ( i , j ) = 1 ] = ∑ i = 1 n ∑ j = 1 m [ ∃ L ,    j ∣ ( k L − 1 ) ] [ gcd ⁡ ( i , j ) = 1 ] \begin{aligned} ans &=\sum_{i=1}^{n}\sum_{j=1}^{m} \left[\exists L,x,\;\text{s.t.}\frac{i}{j}=\frac{x}{k^L-1}\right]\big[\gcd(i,j)=1\big]\\ &=\sum_{i=1}^{n}\sum_{j=1}^{m} \left[\exists L,\;j\mid(k^L{\rm-}1)\right]\big[\gcd(i,j)=1\big] \end{aligned} ans=i=1nj=1m[L,x,s.t.ji=kL1x][gcd(i,j)=1]=i=1nj=1m[L,j(kL1)][gcd(i,j)=1]

显然 i i i 的作用不太大。于是我记 f ( j ) = ∑ i = 1 n [ i ⊥ j ] ,    g ( j ) = [ ∃ L ,    j ∣ ( k L − 1 ) ] f(j)=\sum_{i=1}^{n}\big[i\perp j],\;g(j)=\big[\exists L,\;j\mid(k^L{\rm-}1)\big] f(j)=i=1n[ij],g(j)=[L,j(kL1)],那么
a n s = ∑ j = 1 m f ( j ) g ( j ) ans=\sum_{j=1}^{m}f(j)g(j) ans=j=1mf(j)g(j)

以前的我是这样做的:由于学的是 m i n 25 \tt min25 min25 筛(只要是积性函数就能筛),所以直接考察函数是否为积性函数。比如 g ( x ) g(x) g(x),它是积性函数吗?

我们不难发现 d ∣ x d\mid x dx ( k d − 1 ) ∣ ( k x − 1 ) (k^d{\rm-}1)\mid (k^x{\rm-}1) (kd1)(kx1) 这个结论。证明可以利用因式定理,或者用神奇的
gcd ⁡ ( k a − 1 , k b − 1 ) = k gcd ⁡ ( a , b ) − 1 \gcd(k^a{\rm-}1,k^b{\rm-}1)=k^{\gcd(a,b)}-1 gcd(ka1,kb1)=kgcd(a,b)1

来很容易地说明。然后,以前的我就看出来了 g ( x ) g(x) g(x) 是积性函数。证明很简单,若 a ∣ ( k u − 1 ) a\mid(k^{u}{\rm-}1) a(ku1) b ∣ ( k v − 1 ) b\mid(k^v{\rm-}1) b(kv1),则 a b ∣ ( k u v − 1 )    ( a ⊥ b ) ab\mid(k^{uv}-1)\;(a\perp b) ab(kuv1)(ab) 。而 a ∤ ( k L − 1 ) a\nmid(k^{L}{\rm-}1) a(kL1) 也就不可能有 a b ∣ ( k ν − 1 ) ab\mid(k^{\nu}{\rm-}1) ab(kν1) 了。

于是考虑 g ( p )    ( p ∈ Prime ) g(p)\;(p\in\text{Prime}) g(p)(pPrime) 的值。只要 k k k 不是 p p p 的倍数,就一定有 k φ ( p ) − 1 ≡ 0 ( m o d p ) k^{\varphi(p)}-1\equiv 0\pmod{p} kφ(p)10(modp),故 g ( p ) = [ gcd ⁡ ( k , p ) = 1 ] g(p)=\big[\gcd(k,p)=1\big] g(p)=[gcd(k,p)=1]

进一步, k φ ( p 2 ) − 1 ≡ 0 ( m o d p 2 ) k^{\varphi(p^2)}-1\equiv 0\pmod{p^2} kφ(p2)10(modp2),只要 k k k 不是 p p p 的倍数。那么我们写出

g ( p x ) = [ k ⊥ p ] g(p^x)=[k\perp p] g(px)=[kp]

再利用 g g g 是积性的,我们得到
g ( x ) = [ k ⊥ x ] g(x)=[k\perp x] g(x)=[kx]

现在的我回看该式子:简直是显然啊!原式等价于 k L ≡ 1 ( m o d x ) k^L\equiv 1\pmod{x} kL1(modx),在 k ⊥ x k\perp x kx 时有解 L = φ ( x ) L=\varphi(x) L=φ(x),在 gcd ⁡ ( k , x ) ≠ 1 \gcd(k,x)\ne 1 gcd(k,x)=1 时必然无解。更可笑的是,现在的我连原来这种繁杂的证法也不会……

顺便说一句,这里的 g ( x ) g(x) g(x) 不仅是积性函数了,而且是 完全积性 函数。

那么 f ( j ) f(j) f(j) 怎么算捏?试一试:
f ( j ) = ∑ i = 1 n [ gcd ⁡ ( i , j ) = 1 ] = ∑ i = 1 n ∑ p ∣ i ,    p ∣ j μ ( p ) = ∑ p ∣ j μ ( p ) ⌊ n p ⌋ \begin{aligned} f(j)&=\sum_{i=1}^{n}\big[\gcd(i,j)=1\big]\\ &=\sum_{i=1}^{n}\sum_{p|i,\;p|j}\mu(p)\\ &=\sum_{p|j}\mu(p)\left\lfloor{n\over p}\right\rfloor \end{aligned} f(j)=i=1n[gcd(i,j)=1]=i=1npi,pjμ(p)=pjμ(p)pn

所以我们重写一下开头的式子
a n s = ∑ j = 1 m ∑ p ∣ j μ ( p ) ⌊ n p ⌋ g ( j ) = ∑ p = 1 m ⌊ n p ⌋ μ ( p ) ∑ p ∣ j ,    j ⩽ m g ( j ) = ∑ p = 1 m ⌊ n p ⌋ μ ( p ) g ( p ) ∑ i = 1 ⌊ m p ⌋ g ( i ) \begin{aligned} ans&=\sum_{j=1}^{m}\sum_{p|j}\mu(p)\left\lfloor{n\over p}\right\rfloor g(j)\\ &=\sum_{p=1}^{m}\left\lfloor{n\over p}\right\rfloor\mu(p)\sum_{p|j,\;j\leqslant m}g(j)\\ &=\sum_{p=1}^{m}\left\lfloor{n\over p}\right\rfloor \mu(p)g(p)\sum_{i=1}^{\lfloor{m\over p}\rfloor}g(i) \end{aligned} ans=j=1mpjμ(p)png(j)=p=1mpnμ(p)pj,jmg(j)=p=1mpnμ(p)g(p)i=1pmg(i)

最后一步就用到了 g ( x ) g(x) g(x) 是完全积性函数的性质。对 ⌊ n p ⌋ , ⌊ m p ⌋ \lfloor{n\over p}\rfloor,\lfloor{m\over p}\rfloor pn,pm 同时整除分块,对 μ ( p ) g ( p ) \mu(p)g(p) μ(p)g(p) g ( i ) g(i) g(i) m i n 25 \tt min25 min25 求前缀和即可。

具体怎么求呢?对于 μ ( p ) g ( p ) \mu(p)g(p) μ(p)g(p) g ( i ) g(i) g(i) 的处理方法都一样,以 g ( i ) g(i) g(i) 为例:在 m i n 25 \tt min25 min25 step one \text{step one} step one 中,用 f ( x ) = 1 f(x)=1 f(x)=1 去拟合质数处取值,然后暴力枚举 O ( n ) \mathcal O(\sqrt{n}) O(n ) 个取值点中的 O ( k ln ⁡ k ) \mathcal O(\frac{k}{\ln k}) O(lnkk) 个质数,找到不与 k k k 互质的,减去之。 step two \text{step two} step two 就挺简单了。

然后看了看题解,发现 g ( x ) g(x) g(x) 的前缀和可以更好求,因为 g ( i ) g(i) g(i) 的前缀和显然有长度为 k k k 的 “循环节”。我们可以 O ( k log ⁡ k ) \mathcal O(k\log k) O(klogk) 暴力求出前 k k k 项,而后可以直接算。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 80005;
bool isPrime[MaxN];
vector< int > primes;
int smu[MaxN]; // prefix sum of mu
vector< int > sumf; // real F = mu*g
void sieve(int k){
	memset(isPrime+2,1,MaxN-2);
	smu[1] = 1; // just don't forget it
	sumf.push_back(0); // to be bottom
	for(int i=2,len=0; i<MaxN; ++i){
		if(isPrime[i]){
			primes.push_back(i), ++ len;
			if(k%i == 0) // g(i) = 0
				sumf.push_back(0);
			else sumf.push_back(-1);
		}
		for(int j=0; j<len&&primes[j]<=(MaxN-1)/i; ++j){
			isPrime[i*primes[j]] = false;
			if(i%primes[j] == 0) break;
		}
	}
	for(int i=2,len=primes.size(); i<=len; ++i)
		sumf[i] += sumf[i-1];
}

struct MIN_25{
	int haxi[2][MaxN], w[MaxN];
# define index_(x) (((x) >= MaxN) ? haxi[1][n/(x)] : haxi[0][x])
	int g[MaxN]; // F = mu*g begin with F = 1
	void min_25(int n,int k){
		int id = 0; // allocate index
		for(int i=1; i<=n; i=n/(n/i)+1){
			index_(n/i) = ++ id;
			g[id] = (w[id] = n/i)-1;
		}
		/* Sieve number of primes */ ;
		for(int j=0; primes[j]<=n/primes[j]; ++j)
			for(int i=1; i<=id; ++i){
				if(primes[j] > w[i]/primes[j])
					break; // p_j <= sqrt(w)
				g[i] -= (g[index_(w[i]/primes[j])]-j);
			}
		/* Remove special primes */ ;
		for(int i=id,j=0,x=0; i; --i){
			for(; primes[j]<=k; ++j){
				if(primes[j] > w[i]) break;
				x += !(k%primes[j]); // calc
			}
			g[i] -= x; // remove them
		}
		# define FF(x) ((k%(x)) ? -1 : 0)
		/* Get all ans */ ;
		for(int i=1; i<=id; ++i)
			g[i] = -g[i]; // mu(p)*g(p) = -1
		int j = primes.size();
		for(--j; ~j; --j){ // from big to small
			if(primes[j] > n/primes[j])
				continue; // p_j <= sqrt(n)
			for(int i=1; i<=id; ++i){
				if(primes[j] > w[i]/primes[j])
					break; // good cut branch
				g[i] += FF(primes[j])*(
					g[index_(w[i]/primes[j])]
					- sumf[j+1]); // JiXing
			}
		}
	}
};
MIN_25 fn, fm;

# define sumG(x) (((x)/k)*sxy[k]+sxy[(x)%k])
int sxy[MaxN];
int main(){
	int n = readint(), m = readint();
	int k = readint();
	for(int i=1; i<=k; ++i){
		sxy[i] = sxy[i-1]; // prefix sum
		if(__gcd(i,k) == 1) // coprime
			++ sxy[i]; // add one
	}
	sieve(k); fn.min_25(n,k);
	fm.min_25(m,k); // hc.calc(k);
	int lst = 0, now; int_ ans = 0, fk;
	for(int l=1,r; l<=m&&l<=n; l=r+1){
		r = min(n/(n/l),m/(m/l));
		fk = 1ll*sumG(m/l)*(n/l);
		if(r == n/(n/l)) if(r >= MaxN)
			now = fn.g[fn.haxi[1][n/r]];
			else now = fn.g[fn.haxi[0][r]];
		else if(r >= MaxN)
			now = fm.g[fm.haxi[1][m/r]];
		else now = fm.g[fm.haxi[0][r]];
		ans += fk*(now+1-lst);
		lst = now+1; // mu(1)*g(1) = 1
	}
	printf("%lld\n",ans);
	return 0;
}

后记

去年的 O n e I n D a r k \sf OneInDark OneInDark 声称,他花费了 3 3 3 天才把这题搞出来。今年的 O n e I n D a r k \sf OneInDark OneInDark 却连此题的第一步都推不出来……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值