第6期:数学(持续更新中......)

// 数论初试:2022/1/19

1.欧几里得算法(Euclid algorithm)

求a,b的最大公约数gcd(a,b)

int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}

求a,b的最小公倍数lcm(a,b)

int lcm(int a,int b){
    return a/gcd(a,b)*b;
}
//原理是 gcd(a,b)*lcm(a,b)=a*b

值得注意的是,因为如果直接移项即lcm(a,b)=a*b/gcd(a,b),可能会因为a*b超过int范围,即使最终答案在int范围之内,也有可能导致中间过程越界。

2.Eratosthenes筛法(埃氏筛法)

参考博客 素数基本(埃氏筛法/线性筛法)

“只对一个整数操作,O(N),已经足够了,如果对许多整数进行素性检测,还有更高效的算法,比如埃氏筛法。
问题:枚举n以内所有素数

操作:先把所有整数列出来,然后把2的倍数全部剔除,然后是三的,以此类推,遍历所有素数,把倍数全部划去。

对于每个数字i,如果没被划去,他一定是素数,因为他不是任何2到i-1数字的倍数。然后就开始划它的倍数就好。”

int a[maxx];
int b[maxx+1];
int gg(int n)
{
    int p=0;//记录素数个数
    for(int i=0;i<n+1;i++)b[i]=1;
    b[0]=0;
    b[1]=0;
    //准备完毕
    for(int i=2;i<=n;i++){
        if(b[i]){
            a[p++]=i;//记录素数和个数
            for(int j=2*i;j<=n;j+=i)b[j]=0;//剔除倍数
        }
    }
    return p;//返回素数个数
}

lrj紫书代码

memset(vis,0,sizeof(vis));
for(int i=2;i<=n;i++){
    for(int j=2*i;j<=n;j+=i) vis[j]=1;
}    

改进代码

int m=sqrt(n+0.5);
memset(vis,0,sizeof(vis));
for(int i=2;i<=m;i++){
    if(!vis[i]){
        for(int j=i*i;j<=n;j+=i) vis[j]=1;
    }
}

素数定理:\pi(x)\sim \frac{x}{lnx}
其中,\pi(x)表示不超过x的素数的个数。上述定理的直观含义是:它和\frac{x}{lnx}比较接近。

3.扩展欧几里得算法

参考博客 扩展欧几里得算法

int exgcd(int a,int b,int &x,int &y)//扩展欧几里得算法
{
    if(b==0)
    {
        x=1;y=0;
        return a;  //到达递归边界开始向上一层返回
    }
    int r=exgcd(b,a%b,x,y);
    int temp=y;    //把x y变成上一层的
    y=x-(a/b)*y;
    x=temp;
    return r;     //得到a b的最大公因数
}

lrj紫书

void gcd(int a,int b,int& d,int& x,int& y){
    if(!b){ d=a; x=1; y=0;}
    else{ gcd(b,a%b,d,y,x); y-=x*(a/b);}
}

巫白书

int extgcd(int a, int b, int& x, int& y){
    int d=a;
    if(b!=0){
        d=extgcd(b, a % b, y, x);
        y-=(a/b) * x;
    }else{
        x=1; y=0;
    }
    return d;
}

4. 同余与模算术

模运算公式

(a+b)\ mod\ n=((a\ mod\ n)+(b\ mod\ n))mod\ n

(a-b)\ mod\ n=((a\ mod\ n)-(b\ mod\ n)+n)mod\ n

ab\ mod\ n=(a\ mod\ n)(b\ mod\ n)mod\ n

注意在减法中,由于a mod n 可能小于b mod n,需要在结果加上n,而在乘法中,需要注意

a mod n和b mod n相乘是否会溢出。例如,当n=10^{9}时,ab mod n一定在int范围内,但是

a mod n 和b mod n的乘积可能会超过int。需要用long long保存中间结果,例如:

int mul_mod(int a, int b, int n){
    a %= n; b %= n;
    return (int)((long long)a * b % n);
}

大整数取模

输入正整数n和m,输出n mod m的值。n\leqslant 10^{100},m\leqslant 10^{9}.

分析:首先把大整数写成“自左向右”的形式:1234=((1*10+2)*10+3)*10+4,然后用前面的公式,每步取模,例如:

scanf("%s%d",n,&m);
int len=strlen(n);
int ans=0;
for(int i=0;i<len;i++)
    ans=(int)(((long long)ans*10+n[i]-'0')%m);
printf("%d\n",ans);

幂取模

输入正整数a、n和m,输出a^{n}\ mod\ m 的值。a,n,m\leqslant 10^{9}.

时间复杂度O(n)代码

int pow_mod(int a, int n, int m){
    int ans=1;
    for(int i=0;i<n;i++) ans=(int)((long long)ans * n % m);
}

时间复杂度O(logn)代码(分治法)

int pow_mod(int a, int n, int m){
    if(n==0) return 1;
    int x=pow_mod(a,n/2,m);
    long long ans=(long long)x*x%m;
    if(n%2==1) ans=ans*a%m;
    return (int)ans;
}

模线性方程组

输入正整数a,b,n,解方程ax\equiv b(mod\ n).\ a,b,n\leqslant 10^{9}.

分析:本题中发现了一个新记号:同余。a\equiv b(mod\ n)的含义是“a和b关于模n同余”,即

a mod n=b mod n. 不难得到,a\equiv b(mod\ n)的充要条件是:a-b是n的整数倍。

提示:a\equiv b(mod\ n)的含义是“a和b除以n的余数相同”,其充要条件是“a-b是n的整数倍”.

        这样原来的方程就可以理解成:ax-b是n的正整数值。设这个“倍数”为y,则ax-b=ny,

移项得ax-ny=b,这恰好就是紫书10.1.3节介绍的不定方程(a,n,b是已知量,x和y是未知数)!

唯一需要说明的是,如果x是方程的解,满足x\equiv y(mod\ n)的其他整数y也是方程的解。因此,当谈到同余方程的一个解时,其实指的是一个同余等价类。

特别情况:方程ax\equiv 1(mod\ n)的解称为a关于模n的逆。当gcd(a,n)=1时,该方程有唯一解;否则,该方程无解。

5. 应用举例

1 UVA11582 巨大的斐波那契数! Colossal Fibonacci Numbers!

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

插播快速幂 : P1226 【模板】快速幂||取余运算

快速幂板子

typedef long long ll;

ll mod_pow(ll x, ll n, ll mod){
    if(n==0) return 1;
    ll res = mod_pow(x*x%mod, n/2, mod);
    if(n&1) res=res*x%mod;
    return res;
}

 P1226 Code:

#include<bits/stdc++.h>
#define endl "\n" 
using namespace std;
typedef long long ll;
ll mod_pow(ll x,ll n,ll mod){
	if(n==0) return 1;
	ll res=mod_pow(x*x%mod,n/2,mod);
	if(n&1) res=res*x%mod;
	return res;
}
int main(){
	ios::sync_with_stdio(false); 
	int a,b,p;
	cin>>a>>b>>p;
	cout<<a<<"^"<<b<<" mod "<<p<<"="<<mod_pow(a,b,p)<<endl; 
	return 0;
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

 solution:

        所有计算都是对n取模的,不妨设F(i)=f(i)\ mod\ n。不难发现,当二元组(F(i),F(i+1))出现重复时,整个序列就开始重复。例如,n=3,序列F(i)的前10项为1,1,2,0,2,2,1,0,1,1,第9、10项和前两项完全一样。根据递推公式,第11项会等于第3项,第12项等于第4项.......

不难发现,因为余数最多n种,所以最多n^{2}项就会出现重复。则只需要计算出F(0)~F(n^{2}),然后算出F(a^{b})等于其中的哪一项即可。

code: 

//UVA11582
#include<bits/stdc++.h>
#define endl "\n" 
using namespace std;
typedef unsigned long long ll;
ll a,b;
int n,mod;
int f[1000010]={0,1,1};
//快速幂模板 
int quick_pow(ll a, ll b, int mod){
	a%=mod;
	ll ans=1,base=a;
	while(b>0){
		if(b&1){
			ans*=base;
			ans%=mod;
		}
		base*=base;
		base%=mod;
		b>>=1;
	}
	return ans%mod;
}
void solve(){
	cin>>a>>b>>n;
	if(n==1||a==0){
		cout<<"0"<<endl;
		return;
	}
	for(int i=3;i<=n*n+4;i++){
		f[i]=(f[i-1]+f[i-2])%n;
		if(f[i]==1&&f[i-1]==1){
			mod=i-2; // 回溯,mod可以理解为由n确定的一个周期
			break;
		}
	}
	cout<<f[quick_pow(a,b,mod)]<<endl;
}
int main(){
	ios::sync_with_stdio(false); 
	int T;
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}

学习总结:

看题解的时候发现了 快读的输入、输出版本

inline int read()//输入
{
	int x=0,y=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') y=-y;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	return x*y;
}

inline void write(int x)//输出
{
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

2 UVA12169 不爽的裁判 Disgruntled Judge

一个显然的事实是x[3]=(a*a*x[1]+(a+1)*b)mod\ 10001,那么可以通过扩展欧几里得算法(extgcd)根据x1,x3算出a和b来,每次看看X(2i-1)是不是给定的即可。算(a+1)*b的时候需要用逆元,直接预处理处理,注意要开long long

紫书:“如果知道了a,就可以计算出x2,进而根据x_{3}=(ax_{2}+b)mod\ 10001算出b。有了x1、a和b,就可以在O(T)时间内计算出整个序列了。如果在计算过程中发现和输入矛盾,则这个a是非法的。由于a是0~10000的整数(因为递推公式对10001取模),即使枚举所有的a,时间效率也足够高。”

//UVA12169
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf=1<<30;
const int maxn=4e4+5,mod=10001;
int inv[maxn],in[maxn];

int read(){
	int x=0,y=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') y=-y;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	return x*y;
}

void write(int x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

int extgcd(int a,int b,int& x,int& y){
	if(!b){
		x=1;y=0;
		return a;
	}
	int res=extgcd(b,a%b,x,y);
	int tmp=x;
	x=y;
	y=tmp-a/b*y;
	return res;
}

int getinv(int a){ // ax=1(mod 10001)
	int x,y;
	int tmp=extgcd(a,mod,x,y);
	x/=tmp;
	x=(x%mod+mod)%mod;
	return x;
}

int main(){
	for(int i=0;i<=10000;i++) inv[i]=getinv(i);
	int n; n=read();
	for(int i=1;i<=n;i++) in[2*i-1]=read();
	int a,b;
	for(a=0;a<=10000;a++){
		LL in3=in[3]-1ll*a*a*in[1]; in3=(in3%mod+mod)%mod;
		in3=1ll*in3*inv[a+1]%mod;
		int fg=1;
		b=(int)in3;
		for(int i=2;i<=n*2;i++){
			if(i&1){
				int xi=(1ll*in[i-1]*a+b)%mod;
				if(xi!=in[i]){
					fg=0;
					break;
				}
			}else{
				in[i]=(1ll*in[i-1]*a+b)%mod;
			}
		}
		if(fg==1) break;
	}
	for(int i=2;i<=n*2;i+=2){
		write(in[i]);
		printf("\n");
	}
	return 0;
} 

3 UVA10375 选择与除法 Choose and divide

法一:紫书 分析:本题正是唯一分解定理的用武之地。首先,求出10000以内的所有素数primes,然后用数组e表示当前结果的唯一分解式中各个素数的指数。例如,e={1,0,2,0,0,0,...}表示2^{1}*5^{2}=50.

//UVA10375 紫书
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
int e[maxn],vis[maxn];
vector<int> primes;

//乘以或除以n,其中d=0表示乘,d=-1表示除
void add_integer(int n,int d){
	for(int i=0;i<primes.size();i++){
		while(n%primes[i]==0){
			n/=primes[i];
			e[i]+=d;
		}
		if(n==1) break; // 提前终止循环,节约时间 
	}
} 
//add_factorial(n,d)表示把结果乘以(n!)^d,它的实现如下 
void add_factorial(int n,int d){
	for(int i=1;i<=n;i++){
		add_integer(i,d);
	}
} 

void Is_Prime(){
	for(int i=2;i<=100;i++){
		if(!vis[i]){
			for(int j=i*i;j<=10000;j+=i){
				vis[j]=1;
			}
		}
	}
	for(int i=2;i<=10000;i++){
		if(!vis[i]) primes.push_back(i);
	}
}

int main(){
	Is_Prime();
	int p,q,r,s;
	while(~scanf("%d%d%d%d",&p,&q,&r,&s)){
		memset(e,0,sizeof(e));
		add_factorial(p,1);
		add_factorial(q,-1);
		add_factorial(p-q,-1);
		add_factorial(r,-1);
		add_factorial(s,1);
		add_factorial(r-s,1);
		double ans=1;
		for(int i=0;i<primes.size();i++){
			ans*=pow(primes[i],e[i]);
		}
		printf("%.5lf\n",ans);
	}
	return 0;
}

法二:

处理多项式的时候我们有 ln+exp 的思路,也可以用来处理大数。

(注意没有ln函数,只有log,就是自然对数)

精度容易丢失,要用 long double

#include<bits/stdc++.h>
using namespace std;
long double res=0.0,a,b,c,d;
void add(long double x){while(x>0)res+=log(x--);}
void cut(long double x){while(x>0)res-=log(x--);}
int main(){
	while(scanf("%Lf %Lf %Lf %Lf",&a,&b,&c,&d)==4){
		res=0.0;
		add(a);cut(a-b);cut(b);
		cut(c);add(c-d);add(d);
		printf("%0.5Lf\n",exp(res));
	}
}

法三:组合数可以边乘边除

对于C(m,n)=\frac{m!}{(m-n)!n!},分母有mm项,显然分子有m-n+nm−n+n项,即上下项数相等,当两个组合数相除时,这个性质也满足,所以可以边乘边除。

#include<bits/stdc++.h>

inline int max(int a,int b){return a > b ? a : b;}

int main(){
	int p,q,r,s;
	while(scanf("%d %d %d %d",&p,&q,&r,&s) != EOF){
		double ans = 1.00000;
		int max1 = max(p - q,q);
		int max2 = max(r - s,s);
		int max3 = max(max1,max2);
		for(int i = 1;i <= max3;i++){
			if(i <= max1) ans = ans / i * (p - max1 + i);
			if(i <= max2) ans = ans / (r - max2 + i) * i;
		}
		printf("%.5lf\n",ans);
	}
	return 0;
}

4 UVA10791 最小公倍数的最小和 Minimum Sum LCM

 紫书分析:本题再次用到了唯一分解定理。设唯一分解式n=a_{1}^{p1}*a_{2}^{p2}......,不难发现每个a_{i}^{pi}作为一个单独的整数时最优。

如果就这样匆匆编写程序,可能会掉入陷阱。本题有好几个特殊情况要处理:n=1时答案为1+1=2;n只有一种因子时需要加个1,还要注意n=2^{31}-1时不要溢出。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
// 算出n里面最大能除掉多少d,返回d的k次方
int DivedeAll(int &n, int d){
    int x = 1;
    while (n % d == 0){
        n /= d;
        x *= d;
    }
    return x;
}

ll Solve(int n){
    if (n == 1){ // 注意特判:1 == 1 * 1
        return 2;
    }
    ll Ans = 0; // 最后的答案
    int pf = 0; // 质因子的个数
    int m = (int)sqrt(n); // 不要放在循环里,因为要修改n的值
    for (int i = 2; i <= m; ++i){
        if (n % i == 0){ // i是一个新的质因子
            ++pf;
            Ans += DivedeAll(n, i);
        }
    }
    if (n > 1){  // 如果除到最后还有剩余的,那就把它也加上去
        ++pf;
        Ans += n;
    }
    if (pf <= 1){
        ++Ans;  // 如果n这个数是个质数,那还要加上1(n == 1 * n)
    }
    return Ans;
}
int main(){
    int n, NO = 0;
    while (cin >> n && n){
        cout << "Case " << ++NO << ": " << Solve(n) << endl;
    }
    return 0;
}

题解二:

#include <cstdio>

typedef long long LL;

int nn;

inline void sol(LL n)
{
	int f=0;
	LL ans=0;
	if(n==1)//对1的特判
	{
		printf("Case %d: 2\n",++nn);
		return;
	}
	LL ttt,tn=n;
	for(LL i=2; i*i<=n; ++i)//计算标准分解式,枚举到sqrt即可
	{
		ttt=1;
		if(!(n%i) && (n!=1))
		{
			do
			{
				ttt*=i;
				n/=i;
			}
			while(!(n%i) && (n!=1));
			f++,ans+=ttt;
		}
		if(n==1) break;
	}
	if(tn==n || f==1) ans++;
		//tn==n:n是素数,f==1:n不是素数但除1与n外的因子只有一个
	if(n!=1) ans+=n;//在sqrt(n)以上除n外还有一个n的因子
	printf("Case %d: %lld\n", ++nn, ans);
		//最后一行的换行让我很惊讶(UVa什么时候对输出这么随意了?)
	return;
}

int main()
{
	LL n;
	while(scanf("%lld", &n) && n) sol(n);
	return 0;
}

 题解三:

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
long long n,ans,cnt,q;
int num;
void solve()
{
    int tmp=n;
    while(n%q==0) n/=q;
    if(tmp/n>1) ans+=(tmp/n),num++;
    return;
}//加上px^ax
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n&&n!=0)
    { 
        cnt++;
        ans=0;
        for(q=2;q<=sqrt(n);q++) solve();
        if(n>1) ans+=n,num++;
        if(num==0) ans+=2;
        if(num==1) ans+=1;//特判
        printf("Case %lld: %lld\n",cnt,ans);
        num=0;//清零
    }
    return 0;
}

 5 UVA12716 GCD等于XOR GCD XOR

gcd(a,b)=gcd(a,a-c)=c

 先介绍紫书的解法:

解法一:时间复杂度O(n(logn)^{2})

 本题看上去很难找到简洁的数学公式,因为gcd和xor看上去似乎毫无相干。不过xor的好处是:a\ xor\ b=c,则a\ xor\ c=b,所以可以枚举a和c,然后算出b=a\ xor\ c,最后验证一下是否有gcd(a,b)=c。时间复杂度如何呢?因为c是a的约数,所以和素数筛法类似,时间复杂度为n/1+n/2+...+n/n=O(nlogn)。再加上gcd的时间复杂度为O(logn),所以总的时间复杂度为O(n(logn)^{2})

解法二:时间复杂度O(nlogn)

我们还可以做得更好。上述程序写出来之后,可以打印一些满足gcd(a,b)=a\ xor\ b=c的三元组(a,b,c),然后很容易发现一个现象:c=a-b

证明如下:不难发现a-b\leqslant a\ xor\ b,且a-b\geqslant c。假设存在c使得a-b>c,则c<a-b\leqslant a\ xor\ b,与c=a\ xor\ b矛盾。

有了这个结论,还是沿用上述算法,枚举a和c,计算b=a-c,则gcd(a,b)=gcd(a,a-c)=c,因此只需验证是否有c=a\ xor\ b,时间复杂度降为了O(nlogn)

题解分析:

#include<bits/stdc++.h> 
#define maxn 30000001//事先预处理,不然每次处理依旧会T飞
using namespace std;
int ans[maxn];//ans记录答案
void solve(int n){
	int k=(n>>1);
	for(int b=1;b<=k;b++)//枚举b
	   for(int a=b+b;a<=n;a+=b)//枚举a
	     {
	     	if((a xor b)== a-b )//这里注意位运算的优先级,一定要注意打括号(用^也是同样的问题,卡了好久) 
                ans[a]++;//记录
		 }
	for(int i=2;i<=n;i++)
	ans[i]+=ans[i-1];//前缀和
}
int main(){
	solve(maxn);
	int t,x;
	scanf("%d",&t);
	for(int i=1;i<=t;i++){
		scanf("%d",&x);
		printf("Case %d: %d\n",i,ans[x]);//按要求输出
	}
	return 0;//AC
}

//二 计数与概率基础 2022/01/21

加法原理与容斥原理

加法原理与乘法原理

容斥定理:\left | A\cup B\cup C \right |=|A|+|B|+|C|-|A\cap B|-|B\cap C|-|C\cap A|+|A\cap B\cap C|

有重复元素的全排列

问题描述:有k个元素,其中第i个元素有n_{i}个,求全排列个数。

分析:令所有n_{i}之和为n,再设答案为x。首先做全排列,然后把所有元素编号,其中第s种元素编号为1-n_{s}(例如,有3个a,2个b,先排列成aabba,然后可以编号为a_{1}a_{3}b_{2}b_{1}a_{2})。这样做以后,由于编号后所有元素均不相同,方案总数为n的全排列数n!。根据乘法原理,得到了一个方程:n_{1}!n_{2}!n_{3}!...n_{k}!x=n!,移项即得:x=\frac{n!}{n_{1}!n_{2}!n_{3}!...n_{k}!}

可重复选择的组合

问题描述:有n个不同元素,每个元素可以选多次,一共选k个元素,有多少种方法?例如,n=3,k=2时有6种:(1,1),(1,2),(1,3),(2,2).(2,3),(3,3)

分析:设第i个元素选x_{i}个,问题转化为求方程x_{1}+x_{2}+x_{3}+...+x_{n}=k非负整数解的个数。令y_{i}=x_{i}+1,则答案为y_{1}+y_{2}+y_{3}+...+y_{n}=k+n正整数解的个数。想象有k+n个数字“1”排成一排,则问题等价于:把这些“1”分成n个部分,有多少种方法?这相当于在k+n-1个“候选分割线”中选n-1个,即C(k+n-1,n-1)=C(n+k-1,k)

1 杨辉三角与二项式定理

组合数C_{n}^{m}在组合数学中占有重要地位。与组合数相关的最重要的两个内容是杨辉三角与二项式定理。二项式系数正好和杨辉三角一致。

(a+b)^{n}=\sum_{k=0}^{n}C_{n}^{k}a^{n-k}b^{k}

//时间复杂度为O(n*n)
memset(C,0,sizeof(C));
for(int i=0;i<=n;i++){
	C[i][0]=1;
	for(int j=1;j<=i;j++){
		C[i][j]=C[i-1][j-1]+C[i-1][j];
	}
}

C_{n}^{k}=\frac{n-k+1}{k}C_{n}^{k-1}

//时间复杂度为O(n)
C[0]=1;
for(int i=1;i<=n;i++){
    C[i]=C[i-1]*(n-i+1)/i;
}

注意,应该先乘后除,因为C[i-1]/i可能不是整数。但这样一来增加了溢出的可能性——即使最后结果在int或long long范围之内,乘法也可能溢出。如果担心这样的情况出现,可以先约分,不过一般来说是不必要的。

// 2022/1/22更新

1 UVA1635 无关的元素 Irrelevant Elements

只需要依次计算m的唯一分解式中各个素因子在C_{n-1}^{i-1}中的指数即可完成判断。这些指数仍然可以用 C_{n}^{k}=\frac{n-k+1}{k}C_{n}^{k-1}递推,并且不会涉及高精度。(如果直接递推每个系数除以m的余数,但遗憾的是,递推式中有除法,而模m意义下的逆并不一定存在。)

//待看 待思考
#include<bits/stdc++.h> 
#define MAXN 100010
using namespace std;
// 存素数
int prime[MAXN];
// 存指数
int e[MAXN];
int flag[MAXN];
int counter, n, m;
// 把 m 分解成素数和指数的形式
void calFactors(int x) {
    memset(e, 0, sizeof(e));
    counter = 0;
    int _ = (int)(sqrt(x) + 0.5);
    for (int i = 2; i <= _; i++) {
        if (x % i == 0) {
            prime[counter] = i;
            while (x % i == 0) {
                e[counter]++;
                x /= i;
            }
            counter++;
        }
        if (x == 1) break;
    }
    if (x != 1) {
        prime[counter] = x;
        e[counter++] = 1;        
    }
}
vector<int> solve() {
    memset(flag, 0, sizeof(flag));
    n--;
    vector<int> v;
    for (int i = 0; i < counter; i++) {
        int cure = 0;
        // 因为系数是对称的所以这里其实只用算一半就行了
        // 但是自己比较懒所以就直接从 1 算到 n 了
        for (int j = 1; j < n; j++) {
            int curu = n - j + 1, curd = j;
            while (curu % prime[i] == 0) { 
                curu /= prime[i];
                cure++;
            }
            while (curd % prime[i] == 0) {
                curd /= prime[i];
                cure--;
            }
            if (cure < e[i]) {
                flag[j] = 1;
            }
        }
    }
    for (int i = 1; i < n; i++) {
        if (!flag[i]) v.push_back(i + 1);
    }
    return v;
}
int main() {
    while (~scanf("%d%d", &n, &m)) {
        calFactors(m);
        vector<int> ans = solve();
        printf("%d\n", (int)ans.size());
        for (int i = 0; i < ans.size(); i++) {
            printf("%d%c", ans[i], i == ans.size() - 1 ? '\n' : ' ');
        }
        if (ans.size() == 0) printf("\n"); // 如果答案为 0 的话需要输出一行空行
    }
    return 0;
}

2 数论中的计数问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值