数论练习

1, CF 870F Paths 

大意: n节点图, 节点$x,y$之间有一条边当且仅当$gcd(x,y) ot= 1$.

求$sumlimits_{u=1}^nsumlimits_{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 ightarrow y, space d(x,y)=1$

若$gcd(x,y)=1$且$p_xp_y<=n$, 有$x ightarrow p_xp_y ightarrow y, space d(x,y)=2$

若$gcd(x,y)=1$且$p_xp_y>n$, 有$x ightarrow 2p_x ightarrow 2p_y ightarrow 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
", a1+a2*2+a3*3);
}

练习2. CF 585E Present for Vitalik the Philatelist

大意: n张邮票, 第$i$张价格$a_i$, Vitalik先买一张$a_x$, 店主在剩余邮票中选一个总$gcd$为$g$的邮票集合, 要求$g ot=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)=sumlimits_{x|d}g(d)$

根据莫比乌斯反演有$g(x)=sumlimits_{x|d}mu(frac{d}{x})f(d)$

答案即为$sumlimits_{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
", ans);
}

练习3. CF 803F Coprime Subsequences

大意: 给定序列, 求多少子序列gcd为1.

水题, 按照练习2的套路可以直接秒, 相当于$f(x)=2^{cnt_x}-1$最后求$g(1)$

这里再给出一种不需要求$mu$, 直接筛出$g$的方法

因为$f(x)=sumlimits_{x|d}g(d)$, 就有$g(x)=f(x)-sumlimits_{x|d,x ot= 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
", g[1]);
}

练习4. CF 439E Devu and Birthday Celebration

大意: 求和为n, 长度为k, gcd为1的序列个数

又是一道水题, $f(x)$为$x|gcd$的方案数, 有$f(x)=inom{frac{n}{x}-1}{k-1}$

那么反演一下可以得到$gcd=1$的方案数为$summu(d)f(d)=summu(d)inom{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
", ans);
	}
}

练习5 CF 665F Four Divisors

大意: 求有多少个不超过n的数, 满足只有四个因子.

比较水, 可以发现所有四因子数是两个素数的乘积或者一个素数的三次幂, 转化为求素数个数.

可以用LOJ6235 或者hdu5901的方法

原文地址:https://www.cnblogs.com/uid001/p/10629206.html