【ybt金牌导航8-7-2】周期字符串 / 关于莫比乌斯反演的少量知识

周期字符串

题目链接:ybt金牌导航8-7-2

题目大意

求长度为 n 的只有小写字母组成的循环节长度为 n 的字符串个数。
循环节是最短的复制若干遍后拼起来跟原串相等的字符串。

思路

讲一讲莫比乌斯反演

莫比乌斯反演其实就是不断的用容斥,而莫比乌斯函数就是用来容斥的容斥系数。

\(\varphi(n)=\sum_{d|n}\mu(d)\frac{n}{d}\) 作为例子,我们来看看是怎么容斥的。

首先我们要知道 \(\varphi(n)\) 这个函数原本是干什么的,它叫欧拉函数,是 \(1\sim n\) 中与 \(n\) 互质的数的个数。

那首先 \(d=1\) 的时候,意思就是把所有的数先都塞进答案中,答案加 \(n\)
那如果出现了一个 \(d\) 使得 \(\gcd(n,d)>1\),那 \(d\) 一定是 \(n\) 的某个质因数的倍数。
那你就会想,可以找到每个质因数 \(p\),然后看能有多少个小于等于 \(n\) 的倍数,也就是 \(\frac{n}{p}\) 个,然后就给答案减去这个个数。
当然,你会发现它计算重复了。

如果两个不同的质因数 \(p_1,p_2\),它们乘起来肯定是小于 \(n\),也一定是 \(n\) 的因子。那如果有 \(p_1p_2|d\),那这样的 \(d\) 会被前面的操作中被计算了 \(C_2^1\) 次,那就多扣了一次,那答案就要加回去,那对于这两个质因子,这样的 \(d\)\(\dfrac{n}{p_1p_2}\) 个,那就加回 \(\dfrac{n}{p_1p_2}\)
然后你会发现它还是会重复。

如果三个不同的质因数 \(p_1,p_2,p_3\),那乘起来还是 \(n\) 因子。那如果有 \(p_1p_2p_3|d\),那就被扣除了 \(C_3^1-C_3^2\) 次,少扣了一次,那为了扣除一次,答案又要扣 \(\dfrac{n}{p_1p_2p_3}\)

然后你会发现,它就是加回去,减回去,加回去,减回去不断进行。

那你会看到,当你处理的质因数个数是奇数的时候,它就是扣的操作;是偶数的时候,就是加的操作。
那当质因数个数为 \(k\),这些质因数乘起来是 \(d\),那答案就要加上 \((-1)^k\frac{n}{d}\)
然后你再看莫比乌斯函数的定义,你会发现它就是 \((-1)^k\)。别的时候不管,不需要加减,所以是 \(0\)\(1\) 的时候等于 \(1\) 就是为了一开始的那个全部。

那就是这样的。

那就像上面的东西如果你有一个函数 \(g(m)=\sum\limits_{d|m}f(d)\),你 \(g(m)\) 函数很好求,但是 \(f(d)\) 不好求,你就可以通过莫比乌斯反演求得 \(f(n)\)

关于这道题

我们首先设 \(f_i\) 为循环节长度恰好为 \(i\) 的字符串个数,那题目要的就是 \(f_n\),这点没有问题。

那我们再弄一个 \(g_i\) 为循环节长度恰好为 \(i\) 的因子的字符串个数,那可以看出 \(g_i=\sum\limits_{d|i}f_d\)
那为什么要弄这个 \(g_i\) 呢?我们会想到,如果一个子串可以通过多次复制得到你这个字符串,那字符串长度就一定是这个子串的长度的倍数。那反过来,子串的长度一定是这个字符串长度的因数。
那你会发现,任何长度为 \(i\) 的字符串的循环节长度都是 \(i\) 的因子的字符串个数。
\(g_i=26^i\)

那你会发现,你就可以反演了。
\(g_i=\sum\limits_{d|i}f_d\Rightarrow f_i=\sum\limits_{d|i}g_d\mu_ \frac{m}{d}\)

代码

#include<cstdio>
#define ll long long
#define mo 1000000007

using namespace std;

int n, yz[10001];
ll ans;

ll ksm(ll x, ll y) {//快速幂求 26^x
	ll re = 1;
	while (y) {
		if (y & 1) re = (re * x) % mo;
		x = (x * x) % mo;
		y >>= 1;
	}
	return re;
}

void get_yz() {//求出这个数的所有因子
	for (int i = 1; 1ll * i * i <= n; i++)
		if (n % i == 0) {
			yz[++yz[0]] = i;
			if (i * i != n) yz[++yz[0]] = n / i;
		}
}

ll get_miu(int x) {//求出一个数的 μ 值(定义法)
	if (x == 1) return 1;
	ll re = 1;
	for (int i = 2; 1ll * i * i <= x; i++)
		if (x % i == 0) {
			re *= -1;
			x /= i;
			if (x % i == 0) return 0;
		}
	if (x > 1) re *= -1;
	return re;
}

int main() {
	scanf("%d", &n);
	
	get_yz();
	
	for (int i = 1; i <= yz[0]; i++)
		ans = (ans + (ksm(26, yz[i]) * get_miu(n / yz[i]) % mo + mo) % mo) % mo;
	
	printf("%lld", ans);
	
	return 0;
} 

posted @ 2021-03-27 14:10  あおいSakura  阅读(102)  评论(0编辑  收藏  举报