生物 (裴蜀定理 容斥原理) [2020.5.2]
生物
题目描述
在一个无限长的一维空间中,存在着一个奇特的生物,它的身体上顺次有着 n + 1 个刻印,每个刻印可以用一个正整数来表示。已知它最后一个刻印的值为 m,而其它 n 个刻印的值均不超过 m,并且两个刻印的值可以相同。
这个生物每次可以选中它的任意一个刻印,并且按照这个刻印的值 k,选择向它所在位置的前或后闪烁 k 个单位。我们称可以使得这个生物能够通过若干次闪烁,到达一维空间任何一个位置的刻印序列为超刻印序列。
现在刻印序列显然一共有 $m^n$ 种,为了研究这个生物,请你求出其中超刻印序列的数目。
输入格式
仅一行两个整数,分别为 n, m。
输出格式
输出一行一个整数,表示超刻印序列的数目对 $10^9+7$ 取模的结果。
样例输入
2 4
样例输出
12
数据范围与约定
对于前 20%的数据,保证 n,m <= 5;
对于 100%的数据,保证 1<=n<=15,1<=m<=$10^8$
解题思路
首先转化一下复杂的题面
给定一个序列最后一项是m,之前有n项满足\(1<=n<=m\)。求有多少个序列的最大公因式为1?
令最大公因数为 d,则考虑用 \(m^n\) 减去 d≠1 的情况
注意到序列中一定有值 m,考虑令其质因子分别有 \(p_1, p_2, …, p_k\),故 d 不为 1 时一定为其中之一的倍数
则 d≠1 的情况数即求 { 满足 \(p_1|d\) 的序列 } ∪ { 满足 \(p_2|d\) 的序列 } ∪ … ∪ { 满足 \(p_k|d\) 的序列 } 此并集的大小
由于并集难求,而交集易求,使用容斥原理即可
例如对于求交集{ 满足 \(p_1|d\) 的序列 } ∩ { 满足 \(p_2|d\) 的序列 }大小
则让每个位置的数都是 \(p_1\ p_2\)的倍数即可,交集大小即\((m/p_1/p_2)^n\)
考虑时间复杂度,令 m 的质因子数目为 t,则容斥原理的复杂度为 \(\mathrm{O}(2^t)\)
而且一个数的质因子数目是很少的
对于具有 9 个质因子的数,最小也是\(2*3*5*7*11*13*17*19*23=223092870\)
所以容斥原理显然能够满足时限要求
Code:
Click for the code.
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int n, m, od[105];
int pri[10005], tag[10005], cnt;
int turn[10005], tot;
int len, f;
int ans = 0;
void get_pri() {//朴实无华的欧拉筛
for (int i = 2; i <= 10000; i++) {
if (!tag[i]) {
pri[++cnt] = i;
tag[i] = i;
}
for (int j = 1; j <= cnt && i * pri[j] <= 10000; j++) {
tag[i * pri[j]] = pri[j];
if (i % pri[j] == 0) break;
}
}
}
void init() {//分解质因数
int k = m;
for (int i = 1; i <= cnt; i++) {
if (k % pri[i] == 0) {
turn[++tot] = pri[i];
while (k % pri[i] == 0) k /= pri[i];
}
}
if (k != 1) turn[++tot] = k;
return ;
}
int fast_pow(int base, int k) {//快速幂
int res = 1;
while (k) {
if (k & 1) res = 1ll * res * base % mod;
base = 1ll * base * base % mod;
k >>= 1;
}
return res;
}
void dfs(int pos) {//容斥原理,写的很垃圾,可以更快,但是懒得改
if (pos == len + 1) {
int k = m;
for (int i = 1; i <= len; i++) k /= turn[od[i]];
ans = (ans + f * fast_pow(k, n)) % mod;
return ;
}
for (int i = od[pos - 1] + 1; i <= tot; i++) {
od[pos] = i;
dfs(pos + 1);
}
}
int main() {
// freopen("creature.in", "r", stdin);
// freopen("creature.out", "w", stdout);
cin>>n>>m;
get_pri();
init();
for (int i = 1; i <= tot; i++) {
len = i;
f = (i & 1) ? 1 : -1; //容斥的系数
dfs(1);
}
cout<<((fast_pow(m, n) - ans) % mod + mod) % mod<<endl;
}