数论练习
1, CF 870F Paths
大意: n节点图, 节点$x,y$之间有一条边当且仅当$gcd(x,y)\not= 1$.
求$\sum\limits_{u=1}^n\sum\limits_{v=u+1}^n d(u,v)$, $d$为最短路数组, 不连通时为0.
$1$和$>n/2$的素数显然是孤立点.
考虑其他点对$(x,y)$, 假设$p_x,p_y$为$x,y$的最小素因子.
若$gcd(x,y)>1$, 有$x\rightarrow y, \space d(x,y)=1$
若$gcd(x,y)=1$且$p_xp_y<=n$, 有$x\rightarrow p_xp_y\rightarrow y, \space d(x,y)=2$
若$gcd(x,y)=1$且$p_xp_y>n$, 有$x\rightarrow 2p_x\rightarrow 2p_y\rightarrow y, \space d(x,y)=3$
分别对这三类点计数即可.
对于情况1, 直接欧拉函数求和.
对于情况2, 感觉不是很好算, 但是可以欧拉函数算出情况2与情况3的和
对于情况3, x,y中一定有一个是$>\sqrt{n}$的素数, 我们枚举这个素数计算即可.
复杂度$O(n)$.
const int N = 1e7+10; int phi[N], p[N], s[N], a[N], cnt, vis[N]; void init(int n) { phi[1] = 1; for (int i=2; i<=n; ++i) { if (!vis[i]) p[++cnt]=i,phi[i]=i-1, ++a[cnt]; for (int j=1,t; j<=cnt&&i*p[j]<=n; ++j) { vis[t=i*p[j]] = 1, ++a[j]; if (i%p[j]==0) {phi[t]=phi[i]*p[j];break;} phi[t]=phi[i]*phi[p[j]]; } } REP(i,1,cnt) s[i]=s[i-1]+a[i]; } int main() { int n; scanf("%d", &n), init(n); ll a1 = 0, a2 = 0, a3 = 0, sum = 0, tot = 0; REP(i,2,n) a1 += i-1-phi[i], tot += phi[i]-1; int now = cnt; REP(i,1,cnt) if ((ll)p[i]*p[i]>n) { while ((ll)p[i]*p[now]>n) --now; if (p[i]<=n/2) a3 += s[i-1]-s[now]; sum += s[i-1]-s[now]; } a2 = tot-sum; printf("%lld\n", a1+a2*2+a3*3); }
练习2. CF 585E Present for Vitalik the Philatelist
大意: n张邮票, 第$i$张价格$a_i$, Vitalik先买一张$a_x$, 店主在剩余邮票中选一个总$gcd$为$g$的邮票集合, 要求$g\not=1$且$gcd(a_x,g)=1$, 求所有合法方案数.
经典计数题, 设$f(x)$为$x|g$的方案数, $g(x)$为$x=g$的方案数, $cnt_x$为$x|a_i$的$a_i$个数
有$f(x)=(n-cnt_x)(2^{cnt_x}-1), f(x)=\sum\limits_{x|d}g(d)$
根据莫比乌斯反演有$g(x)=\sum\limits_{x|d}\mu(\frac{d}{x})f(d)$
答案即为$\sum\limits_{x=2}^ng(x)=f(1)-g(1)=-g(1)$
要注意这里$g(1)$是没有实际意义的, 只是为了反演计算.
const int N = 1e7+10; int mu[N], p[N], cnt, vis[N]; void init(int n) { mu[1] = 1; for (int i=2; i<=n; ++i) { if (!vis[i]) p[++cnt]=i,mu[i]=-1; for (int j=1,t; j<=cnt&&i*p[j]<=n; ++j) { vis[t=i*p[j]] = 1; if (i%p[j]==0) {mu[t]=0;break;} mu[t]=-mu[i]; } } } int n, a[N]; int main() { scanf("%d", &n); int ma = 0; REP(i,1,n) {int t; scanf("%d", &t),++a[t],ma=max(ma,t);} init(ma); int ans = 0; REP(i,1,ma) if (mu[i]) { int c = 0; for (int j=i; j<=ma; j+=i) c+=a[j]; (ans-=mu[i]*(n-c)*(qpow(2,c)-1)%P)%=P; } if (ans<0) ans+=P; printf("%d\n", ans); }
练习3. CF 803F Coprime Subsequences
大意: 给定序列, 求多少子序列gcd为1.
水题, 按照练习2的套路可以直接秒, 相当于$f(x)=2^{cnt_x}-1$最后求$g(1)$
这里再给出一种不需要求$mu$, 直接筛出$g$的方法
因为$f(x)=\sum\limits_{x|d}g(d)$, 就有$g(x)=f(x)-\sum\limits_{x|d,x\not= d}g(d)$, 我们可以倒序枚举即可.
const int N = 1e7+10; int n, a[N], g[N]; int main() { scanf("%d", &n); int ma = 0; REP(i,1,n) {int t; scanf("%d", &t),++a[t],ma=max(ma,t);} PER(i,1,ma) { int c = 0; for (int j=i; j<=ma; j+=i) c+=a[j],(g[i]-=g[j])%=P; (g[i]+=qpow(2,c)-1)%=P; } if (g[1]<0) g[1]+=P; printf("%d\n", g[1]); }
练习4. CF 439E Devu and Birthday Celebration
大意: 求和为n, 长度为k, gcd为1的序列个数
又是一道水题, $f(x)$为$x|gcd$的方案数, 有$f(x)=\binom{\frac{n}{x}-1}{k-1}$
那么反演一下可以得到$gcd=1$的方案数为$\sum\mu(d)f(d)=\sum\mu(d)\binom{\frac{n}{d}-1}{k-1}$
const int N = 1e5+10; int mu[N], vis[N], p[N], cnt; ll fac[N], ifac[N]; void init(int n) { mu[1] = 1; for (int i=2; i<=n; ++i) { if (!vis[i]) p[++cnt]=i,mu[i]=-1; for (int j=1,t; j<=cnt&&i*p[j]<=n; ++j) { vis[t=i*p[j]] = 1; if (i%p[j]==0) {mu[t]=0;break;} mu[t]=-mu[i]; } } fac[0] = ifac[0] = 1; REP(i,1,n) fac[i]=fac[i-1]*i%P,ifac[i]=inv(fac[i]); } int C(int n, int m) { if (n<m) return 0; return fac[n]*ifac[m]%P*ifac[n-m]%P; } int main() { init(N-1); int q; scanf("%d", &q); while (q--) { int n, f, ans = 0; scanf("%d%d", &n, &f); int mx = sqrt(n+0.5); REP(i,1,mx) if (n%i==0) { (ans+=mu[i]*C(n/i-1,f-1))%=P; if (i!=n/i) (ans+=mu[n/i]*C(i-1,f-1))%=P; } if (ans<0) ans+=P; printf("%d\n", ans); } }
练习5 CF 665F Four Divisors
大意: 求有多少个不超过n的数, 满足只有四个因子.
比较水, 可以发现所有四因子数是两个素数的乘积或者一个素数的三次幂, 转化为求素数个数.
可以用LOJ6235 或者hdu5901的方法