• 数论练习


    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的方法

  • 相关阅读:
    PHP编程基础学习(一)——数据类型
    6-6 带头结点的链式表操作集(20 分)
    6-5 链式表操作集(20 分)
    6-4 链式表的按序号查找(10 分)
    6-3 求链式表的表长(10 分)
    6-2 顺序表操作集(20 分)
    6-1 单链表逆转(20 分)
    学生成绩管理系统(六):项目总结
    学生成绩管理系统(五):系统的完善与数据库的链接
    学生成绩管理系统(四)
  • 原文地址:https://www.cnblogs.com/uid001/p/10629206.html
Copyright © 2020-2023  润新知