之前做过不少的数论题,关于莫比乌斯与积性函数的数论题挺多的。。。特地过来总结一下。。当作自己的一个回顾了-_-
先安利一下神犇tls的博客和神犇PoPoQQQ的pdf !
膜拜tls…
跪popoqqq…
还有IOI金牌神犇任之州的集训队论文,都是好文啊!
需要先知道线性筛这个东西。。
orz…
线性筛的思想是每个合数都只会被它最小的质因数筛去,通过线性筛,我们可以O(n)得到1到n内一些数论函数的值,比如说欧拉函数、莫比乌斯函数、因子个数等等。。。
本文的除法均为整除,除非特别指出。
[expresion]为bool表达式,值为0或1,当且仅当expresion为真时为1,否则为0。
(i,j)表示gcd(i,j)。
首先定义莫比乌斯函数
莫比乌斯函数的性质:
1) ∑d|nu(d)=⎧⎩⎨1,0,if n = 1其它
有了上述这个式子,我们就可以直接简单地以O(nlogn)筛出1到n内所有数的莫比乌斯函数值了~
至此我们已经有两种办法求1到n内所有数的欧拉函数值了。
//O(n)
bool vis[N];
int primes[N], miu[N];
int init(int n) {
int tot = 0;
miu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
primes[tot++] = i;
miu[i] = -1;
}
for (int j = 0; j < tot; j++) {
int k = i * primes[j];
if (k > n)break;
vis[k] = true;
if (i % primes[j]) miu[k] = -miu[i];
else break;
}
}
}
//O(nlogn)
void init(int n) {
miu[1] = 1;
int t = n >> 1;
for (int i = 1; i <= t; i++) if (miu[i]) {
for (int j = i << 1; j <= n; j += i) miu[j] -= miu[i];
}
}
2) ∑d|nu(d)d=φ(n)n
莫比乌斯函数除了可以用在莫比乌斯反演中之外,还可以用来进行容斥。
举一个常见的例子,求取1到n这个区间内有多少个数与x互质,一般的做法是直接进行容斥,但是我们可以发现容斥的系数刚好是莫比乌斯函数,即ans = ∑d|xu(d)∗nd ,其实这两者从本质考虑是完全等价的。
莫比乌斯反演是一个这样的式子:
定义 F(n)=∑d|nf(n) ,那么可以得到 f(n)=∑d|nu(nd)F(d)
莫比乌斯反演还有一种更常见的形式: F(n)=∑n|df(d) , 那么有 f(n)=∑n|du(dn)F(d) 。
一般应用的都是上述的第二种形式,证明可以通过归纳得出,也可以直接通过式子变换得出,还可以由狄利克雷卷积证明。
f(n)=∑d|nu(nd)F(d)=∑d|nu(nd)∑x|df(x)=∑d|nf(d)∑x|ndu(x)=f(n)
上述的证明中给出了一种常见的和式变换技巧:交换求和顺序。通过交换求和顺序,我们往往可以将某些和式化简或者更容易求出该和式,后面公式的化简将多次用到。
设 f(x) 为一个数论函数,如果对于任意正整数a、b满足 (a,b)=1 ,有 f(ab)=f(a)∗f(b) 的话,我们称 f(n) 为积性函数;如果对于任意正整数啊a、b有 f(ab)=f(a)∗f(b) 的话,我们称 f(n) 为完全积性函数。
常见的积性函数有:
因子个数函数 d(n) ,因子和函数 σ(n) ,二元函数 gcd(a,b) ,欧拉函数 φ(n) ,莫比乌斯函数 u(n) 。
完全积性函数有:
元函数 e(n)=[n==1] ,恒等函数 I(n)=1 ,单位函数 id(n)=n 。
我们来看看积性函数的应用:
如果 f(x) 为积性函数且 n=∑ti=1peii ,那么可以得到 f(n)=∏ti=1f(peii) ,如果f(x)为完全积性函数,那么我们还可以进一步得到 f(n)=∏ti=1f(pi)ei 。
举个简单的例子:因子个数 d(n)=∏ti=1d(peii)=∏ti=1(ei+1) ,因子和函数 σ(n)=∏ti=1σ(peii)=∏ti=1piei+1−1pi−1 。
积性函数还可以用来进行线性筛从而得到很多数论函数的值~
比如下面的欧拉函数:
可以知道当 i%p==0 时, φ(i∗p)=φ(i)∗p ,而当 i%p!=0 时,有
const int N = 1e6 + 5;
bool vis[N];
int phi[N], p[N], cnt = 0;
void seive() {
cnt = 0;
phi[1] = 1;
for (int i = 2; i < N; i++) {
if (!vis[i]) p[cnt++] = i, phi[i] = i - 1;
for (int j = 0; j < cnt; j++) {
int s = i * p[j];
if (s > N) break;
vis[s] = 1;
if (i % p[j] == 0) {
phi[s] = p[j] * phi[i];
break;
}
phi[s] = (p[j] - 1) * phi[i];
}
}
}
积性函数的和也是积性函数,积性函数前缀和是一个常见的问题,常常需要低于线性时间内解决。
狄利克雷卷积是解决积性函数前缀和问题的一个重要工具。
对两个算术函数f, g,定义其Dirichlet卷积为新函数f * g,满足 (f∗g)(n)=∑d|nf(d)g(nd) 。
狄利克雷卷积满足以下定律:
若f,g均为积性函数,则f*g也是积性函数。
我们可以O(nlogn)预处理两个函数的Dirichlet卷积。
LL f[N], g[N], h[N];
void calc(int n) {
for (int i = 1; i * i <= n; i++) {
h[i * i] += f[i] * g[i];
for (int j = i + 1; i * j <= n; j++) h[i * j] += f[i] * g[j] + f[j] * g[i];
}
}
很多数论函数都可以用狄利克雷卷积来表示:
①约数个数函数 d(n)=(I∗I)(n) ;
②约数和函数 d(n)=(I∗id)(n) ;
③ ∑d|nu(d)=[n==1] ,得到 I∗u=e 。
④ ∑d|nφ(d)=n ,得到 I∗φ=id 。
现在我们可以通过狄利克雷卷积来证明莫比乌斯反演了:
如果 F(n)=∑d|nf(n) ,那么F = I*f,那么可以知道F*u = I*f*u=f*(I*u)=f*e=f,所以 f(n)=∑d|nu(nd)F(d) 。
设f(n)为一个数论函数,需要计算 S(n)=∑ni=1f(i) ,通常n>=1e9。
要解决上述问题,我们可以构造一个 S(n) 关于 S(ni) 的递推式。
找到一个合适的数论函数g(n),
下面给出求积性函数前缀和时常用到的一些公式和结论:
① ∑d|nu(d)=[n==1] 。
② ∑d|nφ(d)=n 。
③ ∑ni=1i∗[(i,n)==1]=n∗φ(n)+[n==1]2 。
④ d(n2)=∑d|n2w(d)=∑d|n∑i|du2(i) ,w(d)表示d的不同质因子的个数。
⑤ d(n∗m)=∑i|n∑j|m[(i,j)==1]。
⑥
bzoj 2420 完全平方数
传送门
题意:求第n个无平方因子数。
分析:问题等价于求最小的x使得1到x内有n个无平方因子数,我们考虑容斥,总数减掉2^2的倍数个数,3^2的倍数个数,5^2的倍数个数….加上(2*3)^2的倍数个数,加上(3*5)^2的倍数个数,加上(3*5)^2的倍数个数…..可以发现容斥的系数就是莫比乌斯函数。这样我们二分x,然后 x√ 求个数即可。
不过注意到m/(i*i)的值是分块的,所以我们可以分块求,所以复杂度下界大概是 x√−−−√ ?
#include
using namespace std;
typedef long long LL;
const int N = 41005;
bool vis[N];
int primes[N], miu[N];
int init(int limit) {
int tot = 0;
miu[1] = 0;
for (int i = 2; i <= limit; i++) {
if (!vis[i]) {
primes[tot++] = i;
miu[i] = 1;
}
for (int j = 0; j < tot; j++) {
int k = i * primes[j];
if (k > limit) break;
vis[k] = true;
if (i % primes[j]) miu[k] = -miu[i];
else break;
}
miu[i] += miu[i-1];
}
}
LL cal(LL m) {
LL s = 0;
for (LL i = 1, last; i * i <= m; i = last + 1) {
LL x = m / (i * i); last= sqrt(m / x);
s += (miu[last] - miu[i-1]) * x;
}
return m - s;
}
int main() {
init(41000);
int t;
scanf("%d", &t);
while (t--) {
LL k;
scanf("%lld", &k);
LL l = 0, r = 1644934090LL;
while (r - l > 1) {
LL m = (r + l) >> 1;
cal(m) >= k ? r = m : l = m;
}
printf("%lld\n", r);
}
return 0;
}
bzoj 2301 Problem b
传送门
题意:对于给出的 n 个询问,每次求有多少个数对 (x,y) ,满足 a ≤ x ≤ b , c ≤ y ≤ d ,且 gcd(x,y) = k , gcd(x,y) 函数为 x 和 y 的最大公约数。
分析:我们考虑容斥原理分成四段求,那么现在问题变为了求1~n/k,1到m/k内有多少对数(x,y)最大公约数为1了。
#include
using namespace std;
typedef long long LL;
const int N = 5e4 + 5;
bool vis[N];
int primes[N], miu[N], sum[N];
int init(int n) {
int tot = 0;
sum[1] = miu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
primes[tot++] = i;
miu[i] = -1;
}
for (int j = 0; j < tot; j++) {
int k = i * primes[j];
if (k > n)break;
vis[k] = true;
if (i % primes[j]) miu[k] = -miu[i];
else break;
}
sum[i] = sum[i-1] + miu[i];
}
}
LL solve(int n, int m) {
if (n > m) swap(n, m);
LL ans = 0;
for (int i = 1, last; i <= n; i = last + 1) {
last = min(n / (n / i), m / (m / i));
if (sum[last] - sum[i-1]) ans += (LL)(n / i) * (m / i) * (sum[last] - sum[i-1]);
}
return ans;
}
int main() {
init(5e4);
int t;
scanf("%d", &t);
while (t--) {
int a, b, c, d, k;
scanf("%d %d %d %d %d", &a, &b, &c, &d, &k);
a--; c--;
a /= k; b /= k; c /= k; d /= k;
printf("%lld\n", solve(b, d) + solve(a, c) - solve(a, d) - solve(c, b));
}
return 0;
}
bzoj 2820 YY的GCD
传送门
题意:给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对。
分析:按照上题的套路,我们知道
#include
using namespace std;
typedef long long LL;
const int N = 1e7 + 5;
bool vis[N];
int primes[N], miu[N], s[N];
void init(int n) {
int tot = 0;
miu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
primes[tot++] = i;
miu[i] = -1;
}
for (int j = 0; j < tot; j++) {
int k = i * primes[j];
if (k > n)break;
vis[k] = true;
if (i % primes[j]) miu[k] = -miu[i];
else break;
}
}
for (int i = 0; i < tot; i++) {
for (int j = primes[i], k = 1; j <= n; j += primes[i], k++) s[j] += miu[k];
}
for (int i = 1; i <= n; i++) s[i] += s[i-1];
}
int main() {
init(1e7);
int T;
scanf("%d", &T);
while (T--) {
LL n, m;
scanf("%lld %lld", &n, &m);
if (n > m) swap(n, m);
LL ans = 0;
for (int i = 1, last; i <= n; i = last + 1) {
last = min(n / (n / i), m / (m / i));
if (s[last] - s[i - 1]) ans += (n / i) * (m / i) * (s[last] - s[i - 1]);
}
printf("%lld\n", ans);
}
return 0;
}
bzoj 3529 数表
传送门
题意:给q个询问,每个询问给出n,m,a,求 ∑ni=1∑mj=1F((i,j))∗[(i,j)<=a] ,F(i)表示i的约数和。
分析:出题人相比上面几题加了一点套路而已-_-。
对于这种带有限制的计数问题,如果不好直接解决,我们可以先直接分析,然后在考虑限制,后面往往可以直接算满足限制的,或者容斥,当然这里与容斥无关。。
先不管a,令ans = \sum_{i=1}^{n}\sum_{j=1}^{m}F((i, j))=\sum_{g=1}^{min(n,m)}F(g) * num(g) ∑ni=1∑mj=1F((i,j))=∑min(n,m)g=1F(g)∗num(g) ,这里num(g)表示有多少对(i,j)使得(i,j)==g。那么可以知道
ans = \sum_{i=1}^{n}\sum_{j=1}^{m}F((i, j))=\sum_{g=1}^{min(n,m)}F(g) * \sum_{i=1}^{min(\frac{n}{g},\frac{m}{g})}u(i) * \frac{n}{gi}*\frac{m}{gi}
#include
using namespace std;
const int N = 100001, mod = 0x7fffffff;
bool vis[N];
int p[N], g[N], miu[N], tmp[N], ans[N];
struct que{
int n, m, a, id;
bool operator < (const que& x) const { return a < x.a; }
}q[20005];
struct dsum{
int s, id;
bool operator < (const dsum& x) const { return s < x.s; }
}d[N];
int qpow(int x, int n) {
int res = 1;
while (n) {
if (n & 1) res *= x;
x *= x; n >>= 1;
}
return res;
}
void init(int n) {
miu[1] = 1; int k = 0;
d[1].id = d[1].s = 1;
for (int i = 2; i <= n; i++) {
d[i].id = i;
if (!vis[i]) p[k++] = i, g[i] = 1, tmp[i] = d[i].s = 1 + i, miu[i] = -1;
for (int j = 0; j < k; j++) {
int m = i * p[j];
if (m > n) break;
vis[m] = 1;
if (i % p[j] == 0) {
g[m] = g[i] + 1;
tmp[m] = tmp[i] + qpow(p[j], g[m]);
d[m].s = d[i].s + d[i].s / tmp[i] * (tmp[m] - tmp[i]);
break;
}
miu[m] = -miu[i];
g[m] = 1; tmp[m] = 1 + p[j];
d[m].s = d[i].s * tmp[m];
}
}
sort(d + 1, d + N);
}
namespace BIT {
int c[N+100];
inline void add(int x, int y) {
for (; x < N; x += x & -x) c[x] += y;
}
inline int get(int x) {
int res = 0;
for (; x; x -= x & -x) res += c[x];
return res;
}
}
int main() {
init(100000);
int Q;
scanf("%d" ,&Q);
for (int i = 0; i < Q; i++) {
q[i].id = i;
scanf("%d %d %d", &q[i].n, &q[i].m, &q[i].a);
if (q[i].n > q[i].m) swap(q[i].n, q[i].m);
}
sort(q, q + Q);
for (int i = 0, cur = 1; i < Q; i++) {
while (cur < N && d[cur].s <= q[i].a) {
for (int j = 1; j * d[cur].id < N; j++) if (miu[j]) BIT::add(j * d[cur].id, d[cur].s * miu[j]);
cur++;
}
int res = 0, n = q[i].n, m = q[i].m;
for (int j = 1, last; j <= n; j = last + 1) {
last = min(n / (n / j), m / (m / j));
res += (BIT::get(last) - BIT::get(j - 1)) * (n / j) * (m / j);
}
ans[q[i].id] = res & mod;
}
for (int i = 0; i < Q; i++) printf("%d\n", ans[i]);
return 0;
}
bzoj 2693 jzptab
传送门
题意:多组询问,求 ∑ni=1∑mj=1lcm(i,j)
分析:先将lcm转化为熟悉的gcd,我们有
#include
using namespace std;
typedef long long LL;
const int N = 10000005;
const LL mod = 100000009;
bool vis[N];
int miu[N], p[N];
LL g[N];
int init(int n) {
int tot = 0;
miu[1] = 1; g[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
p[tot++] = i;
miu[i] = -1;
if ((g[i] = (i - (LL)i * i % mod)) < 0) g[i] += mod;
}
for (int j = 0; j < tot; j++) {
int k = i * p[j];
if (k > n) break;
vis[k] = true;
if (i % p[j] == 0) { g[k] = p[j] * g[i] % mod; break;}
LL t = p[j] - (LL)p[j] * p[j] % mod;
if (t < 0) t += mod;
g[k] = g[i] * t % mod;
miu[k] = -miu[i];
}
}
for (int i = 1; i <= n; i++) {
g[i] += g[i-1];
if (g[i] >= mod) g[i] -= mod;
}
}
int main() {
init(1e7);
int t;
scanf("%d", &t);
while (t--) {
LL n, m;
scanf("%lld %lld", &n, &m);
LL ans = 0;
if (n > m) swap(n, m);
for (LL i = 1, last; i <= n; i = last + 1) {
LL x = n / i, y = m / i;
last = min(n / x, m / y);
x = x * (x + 1) / 2 % mod; y = y * (y + 1) / 2 % mod;
ans = (ans + ((g[last] - g[i-1] + mod) % mod * (x * y % mod)) % mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}
hdu 6053 TrickGCD
传送门
题意:给定数组A,求有多少个不同的数组B满足 1≤Bi≤Ai ,且gcd(B_1,B_2,...B_n)>2 gcd(B1,B2,...Bn)>2 。
分析:相信做完上面的题目之后很容易分析得出ans = \sum_{T=1}^{min(A_1,A_2,...A_n)}-u(T)*\prod_{i=1}^{n}\frac{A_i}{T} ans=∑min(A1,A2,...An)T=1−u(T)∗∏ni=1AiT 。多校时这题做到这就不知道怎么优化计算了。。然后各种优化还是超时。。。全世界都会优化的题我不会。。或者这就是弱渣吧。
此处分块没任何意义,我们考虑暴力枚举T,快速计算\prod_{i=1}^{n}\frac{A_i}{T} ∏ni=1AiT ,注意到数据的范围不大,我们可以分段计算,在kT到(k+1)T-1这一段的数除以T都为k,这样子我们只需要统计出cnt[]数组的前缀和,对于每一个T,暴力枚举其倍数的分段,然后快速幂计算即可。总复杂度O(nlog^2n) O(nlog2n) 。
另外我们也可以通过容斥原理得到上述的式子,此处只需要考虑无平方因子数即可,我们加上以2为公约数的序列个数,加上以3为公约数的序列个数,加上以5为公约数的序列个数….减去以2*3为公约数的序列个数,减去以3*5为公约数的序列个数,减去以2*5为公约数的序列个数…可以发现容斥的系数刚好是莫比乌斯函数值的相反数。
#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
const LL mod = 1e9 + 7;
bool vis[N];
int prime[N], miu[N], a[N], cnt[N<<1];
void init(int n) {
miu[1] = -1;
for (int i = 1; i <= n; i++) if (miu[i]) {
for (int j = i << 1; j <= n; j += i) miu[j] -= miu[i];
}
}
LL qpow(LL x, LL a) {
LL res = 1;
while (a) {
if (a & 1) res = res * x % mod;
x = x * x % mod;
a >>= 1;
}
return res;
}
int main() {
init(1e5);
int t, ca = 0;
scanf("%d", &t);
while (t--) {
int n, mi = 100000;
scanf("%d", &n);
for (int i = 0; i <= 200000; i++) cnt[i] = 0;
while (n--) {
int x;
scanf("%d", &x);
cnt[x]++;
mi = min(mi, x);
}
LL ans = 0;
for (int i = 1; i <= 200000; i++) cnt[i] += cnt[i - 1];
for (int i = 2; i <= mi; i++) if (miu[i]) {
LL s = 1;
for (int j = i << 1, k = 2; j <= 100000; j += i, k++) s = s * qpow(k, cnt[j + i - 1] - cnt[j - 1]) % mod;
ans = (ans + s * miu[i] + mod) % mod;
}
printf("Case #%d: %lld\n", ++ca, ans);
}
return 0;
}
hdu 5382
传送门
题意:定义F(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[lcm(i,j)+gcd(i,j)>=n] F(n)=∑ni=1∑nj=1[lcm(i,j)+gcd(i,j)>=n] ,求S(n)=\sum_{i=1}^{n}F(i) S(n)=∑ni=1F(i) 。
分析:考虑F(n) F(n) ,这个大于等于不太好考虑,我们不妨看看等于的情况。
令f(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[lcm(i,j)+gcd(i,j)==n] f(n)=∑ni=1∑nj=1[lcm(i,j)+gcd(i,j)==n] ,那么有
f(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[\frac{i*j}{gcd(i,j)}+gcd(i,j)==n]=\sum_{g=1}^{n}\sum_{i=1}^{\frac{n}{g}}\sum_{j=1}^{\frac{n}{g}}[(i,j)==1]*[g(ij+1)==n] =\sum_{g|n}\sum_{i=1}^{\frac{n}{g}}\sum_{j=1}^{\frac{n}{g}}[i*j==\frac{n}{g}-1][(i,j)==1]=\sum_{g|n}2^{G(\frac{n}{g}-1)}
#include
using namespace std;
typedef long long LL;
const int N = 1e6 + 5, mod = 258280327;
int f[N], g[N], h[N], pri[N], p[20];
bool vis[N];
void init(int n) {
g[1] = p[0] = 1;
for (int i = 1; i <= 20; i++) p[i] = p[i-1] << 1;
int k = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) pri[k++] = i, g[i] = 1;
for (int j = 0; j < k; j++) {
int s = i * pri[j];
if (s > n) break;
vis[s] = 1;
if (i % pri[j] == 0) { g[s] = g[i]; break; }
g[s] = g[i] + 1;
}
g[i] = p[g[i]];
}
for (int i = 1; i <= n; i++) {
for (int j = i << 1, k = 2; j <= n; j += i, k++) {
h[j] += g[k-1];
if (h[j] >= mod) h[j] -= mod;
}
}
for (int i = 1; i <= n; i++) {
f[i] = f[i-1] + 2 * i - 1 - h[i-1];
if (f[i] >= mod) f[i] -= mod;
}
for (int i = 1; i <= n; i++) {
f[i] += f[i-1];
if (f[i] >= mod) f[i] -= mod;
}
}
int main() {
init(1e6);
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
printf("%d\n", f[n]);
}
return 0;
}
hdu 5072 Coprime
传送门
题意:给定n个不同的数,求多少个a,b,c满足两两互质或者两两不互质。
分析:千万别看成gcd相等了。。。明显地,从正面考虑是不太现实的,情况数及其多,我们考虑从反面出发,C(n,3)减去不合法的数目即可。
如果一个三元组abc不合法,那么必定存在一个数与其中一个数互质,而与另外一个数不互质,那么我们枚举每一个数a[i],算有多少个数与它互质,有多少个数与它不互质,假设有s个数与a[i]互质,那么a[i]对答案的贡献是s*(n-1-s),注意到这样计算会有重复,比如说7 15 10符合条件,此时7与10互质,但是7 10 15同样符合条件,如果7 15 21符合条件,此时7与21不互质但是15 7 21同样符合条件,所以最后需要除以2去除重复的情况。
算互质的个数可以通过容斥做,之前也有提到过即ans = ∑d|xu(d)∗num(d) ,num(d)表示d的倍数的个数,所以我们只需要O(nlogn)算倍数个数,O(nlogn)更新,O(n)求解不合法的数目即可。
#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int num[N], miu[N], pri[N], cnt[N], a[N];
bool vis[N];
void init() {
int k = 0;
miu[1] = 1;
for (int i = 2; i <= 100000; i++) {
if (!vis[i]) pri[k++] = i, miu[i] = -1;
for (int j = 0; j < k; j++) {
int s = i * pri[j];
if (s > 100000) break;
vis[s] = 1;
if (i % pri[j] == 0) break;
miu[s] = -miu[i];
}
}
}
int main() {
init();
int t, ca = 0;
scanf("%d", &t);
while (t--) {
int n, ma = 0;
scanf("%d", &n);
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++) {
scanf("%d", a + i); vis[a[i]] = 1;
ma = max(ma, a[i]);
}
for (int i = 1; i <= ma; i++) if (miu[i]) {
for (int j = i; j <= ma; j += i) if (vis[j]) num[i]++;
num[i] *= miu[i];
for (int j = i; j <= ma; j += i) cnt[j] += num[i];
}
cnt[1]--;
LL ans = (LL)n * (n - 1) * (n - 2) / 6, s = 0;
for (int i = 0; i < n; i++) s += (LL)cnt[a[i]] * (n - 1 - cnt[a[i]]);
printf("%lld\n", ans - s / 2);
if (t) {
memset(num + 1, 0, ma << 2);
memset(cnt + 1, 0, ma << 2);
}
}
return 0;
}
hdu 6102 GCDispower
传送门
题意:给定1~n(n <= 100000)的一个排列,定义区间[L, R]的power值为
#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct po{
int f, s;
po(int f = 1, int s = 1) : f(f), s(s) {}
};
void read(int& res) {
char c;
while (!(isdigit(c = getchar())));
res = c - '0';
while (isdigit(c = getchar())) res = res * 10 + c - '0';
}
vector q[N], d[N];
vector<int> f[N];
LL c[N], ans[N];
int pos[N], v[N], n, m, a[N], cnt[N];
int lowbit(int x) {
return x & -x;
}
LL get(int x) {
LL res = 0;
while (x) { res += c[x]; x -= lowbit(x); }
return res;
}
void add(int x, LL va) {
int t = x;
while (x <= n) { c[x] += va; x += lowbit(x); }
}
int getnum(int x) {
int res = 0;
for (int i = 0; i < d[x].size(); i++) res += cnt[d[x][i].f] * d[x][i].s;
return res;
}
void upd(int x, bool v) {
for (int i = 0; i < d[x].size(); i++) v ? ++cnt[d[x][i].f] : --cnt[d[x][i].f];
}
void init(int n) {
for (int i = 2; i <= n; i++) if (!v[i]) {
for (int j = i; j <= n; j += i) {
v[j] = 1;
f[j].push_back(i);
}
}
for (int i = 2; i <= n; i++) {
int s = 1 << f[i].size();
for (int j = 0; j < s; j++) {
d[i].push_back(po());
for (int k = 0; k < f[i].size(); k++) {
if (j >> k & 1) d[i][j].f *= f[i][k], d[i][j].s *= -1;
}
}
}
}
void solve() {
for (int i = 1; i <= n; i++) {
int k = 1;
for (int j = a[i] << 1; j <= n; j += a[i]) {
if (pos[j] < i) v[k++] = pos[j];
}
sort(v + 1, v + k);
LL sum = 0;
for (int j = k - 1; j >= 1; j--) {
int va = a[v[j]] / a[i];
sum += (LL)getnum(va) * a[i];
add(v[j-1] + 1, sum); add(v[j] + 1, -sum);
upd(va, 1);
}
for (int j = k - 1; j >= 1; j--) upd(a[v[j]] / a[i], 0);
for (po j : q[i]) ans[j.f] = get(j.s);
}
}
int main() {
init(100000);
int t;
read(t);
while (t--) {
read(n); read(m);
for (int i = 1; i <= n; i++) {
read(a[i]); c[i] = 0;
q[i].clear(); pos[a[i]] = i;
}
for (int i = 1; i <= m; i++) {
int l, r;
read(l); read(r);
q[r].push_back(po(i, l));
}
solve();
for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
}
return 0;
}
hdu 6134 Battlestation Operational
传送门
题意:计算 f(n)=∑ni=1∑ij=1⌈ij⌉[(i,j)==1]。
分析:这是一个明显的前缀和形式,计算 g(i)=∑ij=1⌈ij⌉[(i,j)==1] 。
g(i)=φ(i)−1+∑ij=1ij∗∑d|(i,j)u(d)=φ(i)−1+∑d|iu(d)∑idj=1ijd 。
到了这里,就能够看出来明显的套路了。线性筛因子个数,然后对于每一个数直接枚举其倍数算贡献,最后统计前缀和即可。查询O(1),复杂度 O(nlogn) 。
#include
using namespace std;
typedef long long LL;
const int N = 1e6 + 5, mod = 1e9 + 7;
bool vis[N];
int prime[N], phi[N], g[N];
short miu[N];
LL sum[N], f[N];
void init(int n) {
phi[1] = sum[1] = miu[1] = 1;
int k = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[k++] = i;
g[i] = 1; phi[i] = i - 1;
sum[i] = 2; miu[i] = -1;
}
for (int j = 0; j < k; j++) {
int s = i * prime[j];
if (s > n) break;
vis[s] = 1;
if (i % prime[j] == 0) {
g[s] = g[i] + 1;
sum[s] = sum[i] + sum[i] / g[s];
phi[s] = phi[i] * prime[j];
break;
}
g[s] = 1; sum[s] = sum[i] << 1;
phi[s] = phi[i] * (prime[j] - 1);
miu[s] = -miu[i];
}
}
for (int i = 2; i <= n; i++) sum[i] += sum[i-1];
for (int i = 1; i <= n; i++) {
if (miu[i]) for (int j = i; j <= n; j += i) f[j] = (f[j] + miu[i] * sum[j/i]) % mod;
if (f[i] < 0) f[i] += mod;
}
for (int i = 1; i <= n; i++) f[i] = (f[i] + phi[i] - 1 + f[i-1]) % mod;
}
int main() {
init(1e6);
int n;
while (~scanf("%d", &n)) printf("%lld\n", f[n]);
return 0;
}
接下来来看一些积性函数的问题。
hdu 5628 Clarke and math
传送门
题意:给定f(i) f(i) 序列, 1<=i<=n ,求 g(i)=∑i1|i∑i2|i1∑i3|i2...∑ik|ik−1f(ik) , 1<=i<=n 。
分析: 这里我们可以看出来 g 函数是 f 函数与k个恒等函数 I 的狄利克雷卷积,而狄利克雷卷积满足交换律和结合律,所以我们可以知道 g(n)=(f∗Ik)(n) ,这样我们只需要快速幂计算k个恒等函数的卷积,然后最后在做一次与f函数的卷积就够了,复杂度 O(nlognlogk) 。初始化为元函数 e(n) 即可。
#include
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, mod = 1e9 + 7;
LL ans[N], f[N], g[N], h[N];
int n, k;
void calc(LL* f, LL* g) {
memset(h + 1, 0, n << 3);
for (int i = 1; i * i <= n; i++) {
h[i * i] += f[i] * g[i] % mod;
if (h[i * i] >= mod) h[i * i] -= mod;
for (int j = i + 1; i * j <= n; j++) {
h[i * j] += f[i] * g[j] % mod + f[j] * g[i] % mod;
while (h[i * j] >= mod) h[i * j] -= mod;
}
}
copy(h + 1, h + 1 + n, g + 1);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%lld", f + i), ans[i] = 0, g[i] = 1;
ans[1] = 1;
while (k) {
if (k & 1) calc(g, ans);
calc(g, g); k >>= 1;
}
calc(f, ans);
for (int i = 1; i < n; i++) printf("%lld ", ans[i]);
printf("%lld\n", ans[n]);
}
return 0;
}
hdu 5970 最大公约数
传送门
题意:定义函数f如下:
void f(int x, int y) {
int c = 0;
while (y) {
c++;
int t = x % y;
x = y;
y = t;
}
}
return c * x * x;
给定 n<=666666666,m<=666,p<=666666666 ,求 ∑ni=1∑mj=1i∗jf(i,j) 。
分析:m的范围很小,这个必定是关键点。
分析f函数可以知道,对于 x=i(mody) 的所有x, f(x,y) 函数返回的值都是相同的。我们可以得到
#include
using namespace std;
typedef long long LL;
const int N = 670;
int c[N][N], gcd[N][N];
void init(int n) {
gcd[1][1] = c[1][1] = 1;
for (int i = 2; i <= n; i++) {
gcd[i][i] = i; c[1][i] = 2;
gcd[i][1] = gcd[1][i] = c[i][i] = c[i][1] = 1;
for (int j = 2; j < i; j++) {
if (!gcd[i][j]) gcd[i][j] = gcd[j][i-j];
gcd[j][i] = gcd[i][j];
c[i][j] = c[j][i % j] + 1;
c[j][i] = c[i][j] + 1;
}
}
}
int main() {
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
init(666);
int t;
scanf("%d", &t);
while (t--) {
int n, m, p;
scanf("%d %d %d", &n, &m, &p);
int ans = 0;
for (int j = 1; j <= m; j++) {
for (int i = 1; i <= j && i <= n; i++) {
int _c = c[i][j], g = gcd[i][j] * gcd[i][j];
for (int k = 0; k < _c; k++) {
if (i + k * j > n) break;
LL tm = (n - (i + j * k)) / (_c * j) + 1;
ans += (LL)(i + j * k) * j / (_c * g) * tm % p;
if (ans >= p) ans -= p;
ans += (tm - 1) * tm / 2 % p * (j * j / g) % p;
if (ans >= p) ans -= p;
}
}
}
printf("%d\n", ans);
}
return 0;
}
hdu 5528 Count a * b
传送门
题意: f(m)=∑m−1i=0∑m−1j=0[i∗jmodm!=0] ,求 g(n)=∑m|nf(m) 。
分析:求i*j % mod m != 0不太方便,我们从反面考虑转化为熟悉的问题。
那么答案就是ans = ∑m|nm2−∑m−1i=0∑m−1j=0[i∗jmodm==0] ,这里我们令 f(m)=∑m−1i=0∑m−1j=0[i∗jmodm==0] ,那么
#include
using namespace std;
typedef unsigned long long LL;
const int N = 31630;
bool vis[N];
int prime[N], k;
void init(int n) {
for (int i = 2; i <= n; i++) {
if (!vis[i]) prime[k++] = i;
for (int j = 0; j < k; j++) {
int s = i * prime[j];
if (s > n) break;
vis[s] = true;
if (i % prime[j] == 0) break;
}
}
prime[k] = 40000;
}
LL solve(int n) {
int m = n;
LL all = 1, sum = 1;
for (int i = 0; prime[i] * prime[i] <= n; i++) {
int p = prime[i];
if (n % p == 0) {
int cnt = 0;
while (n % p == 0) n /= p, cnt++;
LL f = 1, s = 1;
for (int j = 0; j < cnt; j++) s *= p, f += s * s;
all *= f;
sum *= (cnt + 1);
}
}
if (n != 1) all *= (LL)n * n + 1, sum <<= 1;
return all - m * sum;
}
int main() {
//freopen("in.txt", "r", stdin);
//freopen("out1.txt", "w", stdout);
init(31625);
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
printf("%llu\n", solve(n));
}
return 0;
}
看到上面那题,忍不住要把一个相似的题拿出来-_-。
csu 1934 计数
传送门
题意:给定n,m,a,求满足x*y%a==0且1<=x<=n,1<=y<=m的(x,y)的对数,n<=1e18,m<=1e18,a<=1e9。
分析:似乎看起来与上题有点相似,但是注意到这里x和y是可以大于a的,所以没办法搞,那么我们从一般的思路考虑。我们只需要枚举a的因子,然后直接算即可,也就是说ans = ∑d|and∗mda 。注意到这里明显是有重复的,所以我们考虑容斥。
对a做质因数分解,假设 a=∏ti=1peii 。算出 x=pf11∗pf22∗...∗pftt(0<=fi<=ei) 的每一种形式下满足条件的对数,这样子就不会有重复了,在算总共 ∏ti=1(ei+1) 每种x下时的数目时如果x关于a的某个质因数 pi 的指数 fi 小于 ei ,那么这时我们就需要容斥减掉这个指数为 fi+1 的情况了~。
似乎说的一点也不清楚?
#include
using namespace std;
typedef long long LL;
const LL mod = 333333333333333331LL;
int pri[31625], fac[13][2], k, s[13], p[13][30], lef[15], val[15];
bool vis[31625];
LL a, b, c, ans, t;
LL mulmod(LL x, LL y) {
if (x >= mod) x %= mod;
if (y >= mod) y %= mod;
LL tmp = (x * y - (LL)((long double)x / mod * y + 1e-8) * mod) % mod;
return tmp < 0 ? tmp + mod : tmp;
}
void init(int n) {
for (int i = 0; i <= 11; i++) lef[i] = 1 << i;
for (int i = 2; i <= n; i++) if (!vis[i]) {
pri[k++] = i;
for (int j = i * i; j <= n; j += i) vis[j] = 1;
} pri[k++] = 31650;
}
void dfs(int i, LL mul, int* s) {
if (i == k) {
int l = 0;
for (int j = 0; j < k; j++) if (s[j] < fac[j][1]) val[l++] = fac[j][0];
LL sum = b / mul;
for (int j = 1; j < lef[l]; j++) {
LL tmp = mul; bool f = 0;
for (int _i = 0; _i < l; _i++) if (j & lef[_i]) f = !f, tmp *= val[_i];
sum += f ? -(b / tmp) : b / tmp;
}
ans += mulmod(sum, c / (t / mul));
if (ans >= mod) ans -= mod;
return ;
}
for (int j = 0; j <= fac[i][1]; j++) {
s[i] = j;
dfs(i + 1, mul * p[i][j], s);
}
}
int main() {
init(31623);
while (~scanf("%lld %lld %lld", &a, &b, &c)) {
if (b < a / c) { puts("0"); continue; }
ans = k = 0; t = a;
for (int i = 0, j = pri[0]; a != 1 && j * j <= t; j = pri[++i]) {
if (a % j == 0) {
int cnt = 0; p[k][0] = 1;
while (a % j == 0) a /= j, ++cnt, p[k][cnt] = p[k][cnt-1] * j;
fac[k][0] = j, fac[k++][1] = cnt;
}
}
if (a > 1) p[k][1] = fac[k][0] = a, p[k][0] = fac[k++][1] = 1;
dfs(0, 1, s);
printf("%lld\n", ans);
}
return 0;
}
51nod 1244 莫比乌斯函数之和
传送门
题意:求 ∑bi=au(i) 。
分析:令 s(n)=∑ni=1u(i) ,那么
#include
using namespace std;
typedef long long LL;
const int N = 1e7 + 5;
unordered_mapint > mp;
bool vis[N];
int prime[N], miu[N];
void init(int n) {
miu[1] = 1;
int k = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) prime[k++] = i, miu[i] = -1;
for (int j = 0; j < k; j++) {
int s = i * prime[j];
if (s > n) break;
vis[s] = 1;
if (i % prime[j] == 0) { break; }
else miu[s] = -miu[i];
}
}
for (int i = 2; i <= n; i++) miu[i] += miu[i-1];
}
LL d(LL n) {
if (n <= 10000000LL) return miu[n];
if (mp[n]) return mp[n];
int res = 1;
for (LL i = 2, last; i <= n; i = last + 1) {
last = n / (n / i);
res -= (last - i + 1) * d(n / i);
}
return mp[n] = res;
}
int main() {
init(1e7);
LL a, b;
while (~scanf("%lld %lld", &a, &b)) printf("%d\n", d(b) - d(a - 1));
return 0;
}
51nod 1244 欧拉函数之和
传送门
题意:求 ∑ni=1φ(i) 。
分析:令 s(n)=∑ni=1φ(i) ,那么
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7, inv = 500000004LL;
const int N = 1e7 + 5;
unordered_map mp;
bool vis[N];
int prime[N], phi[N];
void init(int n) {
phi[1] = 1;
int k = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) prime[k++] = i, phi[i] = i - 1;
for (int j = 0; j < k; j++) {
int s = i * prime[j];
if (s > n) break;
vis[s] = 1;
if (i % prime[j] == 0) {
phi[s] = phi[i] * prime[j];
break;
}
else phi[s] = phi[i] * (prime[j] - 1);
}
}
for (int i = 2; i <= n; i++) phi[i] = (phi[i] + phi[i-1]) % mod;
}
LL d(LL n) {
if (n <= 10000000LL) return phi[n];
if (mp[n]) return mp[n];
LL res = (n % mod * ((n + 1) % mod)) % mod * inv % mod;
for (LL i = 2, last; i <= n; i = last + 1) {
last = n / (n / i);
res = (res - (last - i + 1) * d(n / i) % mod + mod) % mod;
}
return mp[n] = res;
}
int main() {
init(1e7);
LL n;
while (~scanf("%lld", &n)) printf("%lld\n", d(n));
return 0;
}
hdu 5608 function
传送门
题意:求 ∑ni=1f(i) ,且 N2−3N+2=∑d|Nf(d) 。
分析:最基础的杜教筛,和上面两题一样,拿来构造狄利克雷卷积的函数g(n)都是恒等函数I(n)=1。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7, inv1 = 166666668LL, inv2 = 500000004LL;
const int N = 1e6 + 5;
LL f[N];
unordered_map mp;
void init(int n) {
f[1] = 0;
for (int i = 2; i <= n; i++) {
f[i] = ((LL)i * i % mod - 3 * i + mod) % mod + 2;
if (f[i] >= mod) f[i] -= mod;
}
int up = n >> 1;
for (int i = 2; i <= up; i++) {
for (int j = i << 1; j <= n; j += i) f[j] = (f[j] - f[i] + mod) % mod;
}
for (int i = 2; i <= n; i++) f[i] = (f[i] + f[i-1]) % mod;
}
LL get(LL n) {
if (n <= 1000000LL) return f[n];
if (mp.count(n)) return mp[n];
LL res = n * (n + 1) % mod * (n << 1 | 1) % mod * inv1 % mod;
res = (res - 3 * (n * (n + 1) % mod * inv2 % mod) % mod + mod) % mod;
res = (res + 2 * n) % mod;
for (LL i = 2, last; i <= n; i = last + 1) {
last = n / (n / i);
res = (res - (last - i + 1) * get(n / i) % mod + mod) % mod;
}
return mp[n] = res;
}
int main() {
init(1e6);
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
printf("%lld\n", get(n));
}
return 0;
}
51nod 1238 最小公倍数之和
传送门
题意:求 ∑ni=1∑nj=1lcm(i,j),n<=1e10 。
分析:ans = ∑ni=1∑nj=1i∗j(i,j)=∑ng=1g∗∑ngi=1∑ngj=1i∗j∗[(i,j)==1] 。
令 f(n)=∑ni=1∑nj=1i∗j∗[(i,j)==1] 。
可以知道
51nod 1238 最大公约数之和
传送门
题意:求 ∑ni=1∑nj=1gcd(i,j),n<=1e10。
分析:相比上题,本题容易多了。。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7, inv = 500000004LL;
const int N = 1e7 + 5;
unordered_map mp;
bool vis[N];
int prime[N], phi[N];
void init(int n) {
phi[1] = 1;
int k = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) prime[k++] = i, phi[i] = i - 1;
for (int j = 0; j < k; j++) {
int s = i * prime[j];
if (s > n) break;
vis[s] = 1;
if (i % prime[j] == 0) {
phi[s] = phi[i] * prime[j];
break;
}
else phi[s] = phi[i] * (prime[j] - 1);
}
}
for (int i = 2; i <= n; i++) phi[i] = (phi[i] + phi[i-1]) % mod;
}
LL d(LL n) {
if (n <= 10000000LL) return phi[n];
if (mp.count(n)) return mp[n];
LL res = (n % mod * ((n + 1) % mod)) % mod * inv % mod;
for (LL i = 2, last; i <= n; i = last + 1) {
last = n / (n / i);
res = (res - (last - i + 1) * d(n / i) % mod + mod) % mod;
}
return mp[n] = res;
}
int main() {
init(1e7);
LL n;
scanf("%lld", &n);
LL res = 0;
for (LL i = 1, last; i <= n; i = last + 1) {
LL x = n / i; last = n / x; x %= mod; x = x * x % mod;
res = (res + (d(last) - d(i - 1) + mod) % mod * x % mod) % mod;
}
printf("%lld\n", res);
return 0;
}
51nod 1227 平均最小公倍数
传送门
题意:定义 A(n)=∑n