基础数论刷题柱

题单来自于:[2025年暑假集训B班基础数论作业]

jzyz1611 Interval GCD

如果用线段树维护区间的gcd,难以修改

注意到gcd(a,b)=gcd(a,a-b),因此区间[l,r]的gcd可以修改为询问gcd(a[l],a[l]-a[l+1],a[l+1]-a[l+2]...a[r-1]-a[r]),此时询问a[l]是区间加,单点询问,非常简单

设b[i]是a[i]的差分数组,也就是b[i]=a[i]-a[i-1],维护b[i]就可以单点修改区间询问,也不困难

对于区间加,单点询问的线段树可以不下传懒标记,而是把区间加放在区间节点上,单点询问时把路径上的区间加操作加在一起。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010,mod=998244353;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
ll gcd1(ll a,ll b)
{
	a=abs(a);
	b=abs(b);
	if(b==0)
		return a;
	return gcd1(b,a%b);
}
int n,q;
ll a[N],b[N],sum[N*4],lz[N*4],GCD[N*4];
void build(int x,int l,int r)
{
	if(l==r)
	{
		GCD[x]=b[l];
		sum[x]=a[l];
		return ;
	}
	int mid=(l+r)/2;
	build(x*2,l,mid);
	build(x*2+1,mid+1,r);
	GCD[x]=gcd1(GCD[x*2],GCD[x*2+1]);
}
void dan(int x,int l,int r,int pos,ll v)
{
	if(l==r)
	{
		GCD[x]+=v;
		return ;
	}
	int mid=(l+r)/2;
	if(pos<=mid)
		dan(x*2,l,mid,pos,v);
	else
		dan(x*2+1,mid+1,r,pos,v);
	GCD[x]=gcd1(GCD[x*2],GCD[x*2+1]);
}
void qu(int x,int l,int r,int tl,int tr,ll v)
{
	if(tl<=l&&r<=tr)
	{
		lz[x]+=v;
		return ;
	}
	int mid=(l+r)/2;
	if(tl<=mid)
		qu(x*2,l,mid,tl,tr,v);
	if(tr>mid)
		qu(x*2+1,mid+1,r,tl,tr,v);
}
ll askqu(int x,int l,int r,int tl,int tr)
{
	if(tl<=l&&r<=tr)
		return GCD[x];	
	int mid=(l+r)/2;
	ll t=0;
	if(tl<=mid)
		t=gcd1(t,askqu(x*2,l,mid,tl,tr));
	if(tr>mid)
		t=gcd1(t,askqu(x*2+1,mid+1,r,tl,tr));
	return t;
}
//有的时候不需要下传
//把修改挂在完全覆盖的,最少的区间上
//询问时,路径上的lz相加,再加上叶子的 
ll askdan(int x,int l,int r,int pos)
{
	if(l==r)
		return sum[x]+lz[x];	
	int mid=(l+r)/2;
	if(pos<=mid)
		return askdan(x*2,l,mid,pos)+lz[x];
	else
		return askdan(x*2+1,mid+1,r,pos)+lz[x];
}
int main()
{
//	freopen("1.in","r",stdin);
	n=read();q=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1;i<n;i++)
		b[i]=a[i]-a[i+1];
	build(1,1,n);
	for(;q;q--)
	{
		char c;
		cin>>c;
		if(c=='C')
		{
			int l=read(),r=read();ll x=read();
			
			if(l!=1)
				dan(1,1,n,l-1,-x); 
			dan(1,1,n,r,x);
			qu(1,1,n,l,r,x);
		}
		else
		{
			int l=read(),r=read();
			if(l==r)
				cout<<askdan(1,1,n,l)<<'\n';
			else				 
				cout<<abs(gcd1(askqu(1,1,n,l,r-1),askdan(1,1,n,l)))<<'\n';
		}
	}
}
//1、以1为根节点
//2、x的左儿子是 2x,右儿子是 2x+1
//3、x是[l,r],如果l==r叶子没有儿子
//mid=(l+r)/2
//左儿子:l,mid
//右儿子:mid+1,r
//4、线段树4*n空间 
//5、区间修改懒标记
//sum[x]+=(r-l+1)*v
//lz[x]+=v
//表示儿子们应该+v但是还没加 

jzyz1396 [CQOI]余数之和-整除分块

\(k\mod i=k-\lfloor k/i\rfloor *i\),因此可以把\(\sum k \mod i\)变为\(n*k- \sum \lfloor k/i\rfloor*i\)

在整除分块时,lr区间内\(k/i\)的值相同,因此可以加速


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,ans;
int main()
{
	cin>>n>>k;
	ans=n*k;
	for(ll l=1,r;l<=n&&l<=k;l=r+1)
	{
		r=k/(k/l);
		r=min(r,n);
		r=min(r,k);
		ans=ans-(k/l)*(l+r)*(r-l+1)/2;
	}
	cout<<ans;
}

河南萌新联赛2025第(二)场:河南农业大学 A 约数个数和

题目链接

\(\sum d(i)\)可以转化为\(\sum n/i\),于是变成了整除分块模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,ans;
int main()
{
	cin>>n>>k;
	ans=n*k;
	for(ll l=1,r;l<=n&&l<=k;l=r+1)
	{
		r=k/(k/l);
		r=min(r,n);
		r=min(r,k);
		ans=ans-(k/l)*(l+r)*(r-l+1)/2;
	}
	cout<<ans;
}

BZOJ4488 [Jsoi2015]最大公约数

如果固定右左端点,则区间gcd随着右端点向右移动而减小,每次减小必然除以二或者除的更多,因此突变点最多有log个

接下来只需移动左端点时更新突变点和区间gcd,枚举和gcd更新答案即可。维护突变点稍有难度,考虑左端点从大往小变,这样原来不是突变点的依然不是突变点,因为原来不是突变点说明\(\gcd(a[l+1],a[l+2]...a[pos-2],a[pos-1])|a[pos]\),那么加上a[l]后,依然满足\(\gcd(a[l],a[l+1],a[l+2]...a[pos-2],a[pos-1])|a[pos]\),依然不会是突变点。如果原来是突变点,那么加上\(a[l]\)后可能是突变点也可能不是。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010,mod=998244353;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
int n;
ll a[N],ans;
vector<pair<int,ll>>o[N];
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int l=n;l>=1;l--)
	{
		o[l].push_back({l,a[l]});
		for(auto [pos,v]:o[l+1])
		{
			if(__gcd(v,a[l])==o[l].back().second)
				continue;
			else
				o[l].push_back({pos,__gcd(v,a[l])});
		}
		for(int j=0;j<o[l].size();j++)
		{
			if(j==o[l].size()-1)
				ans=max(ans,o[l][j].second*(n-l+1));
			else
				ans=max(ans,o[l][j].second*(o[l][j+1].first-l));
		}
	}
	cout<<ans; 
}
//随着r增大
//gcd要么不变,要么变小
//变小至少除以二
//最多变小log(1e12)次

 
//二分突变点不太行
//已知l+1的全体突变点
//怎么拿到l的全体突变点
//pos=l
//原来突变的,这次有可能突变
//a[pos]不整除gcd(l+1,pos-1)
//a[pos]      gcd(l,pos-1)
//原来不突变的,这次一定不突变
//gcd(l+1,pos-1)|a[pos]
//gcd(l,pos-1)|gcd(l+1,pos-1)
//--->
//gcd(l,pos-1)|a[pos]

p2492. 习题10.2.4 质因数分解

\(n\)只有\(10^{12}\),使用暴力的\(t*\sqrt{10^{12}}\)即可。

如果预处理质数,用质数分解质因数会更快一点。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010,mod=998244353;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
void work()
{
	ll n=read();
	for(ll i=2;i<=n/i;i++)
	{
		while(n%i==0)
		{
			n=n/i;
			cout<<i<<' ';
		}
	}
	if(n!=1)
		cout<<n<<' ';
	cout<<'\n';
}
int main()
{
	freopen("prime.in","r",stdin);
	freopen("prime.out","w",stdout);
	for(int t=read();t;t--)
		work();
}

loj2314. 「NOIP2017」小凯的疑惑

证明我也不会,结论是输出\(a*b-a-b\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010,mod=998244353;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
int main()
{
	ll a=read(),b=read();
	cout<<a*b-a-b;
}

jzyz381. 【bzoj2190】[SDOI2008]仪仗队

1、可以发现\((2,6)\)不能被看见,因为被\((1,3)\)挡住了,可以发现一般情况下需要\(gcd(x,y)\neq 1\)才能被看见,否则\((x,y)\)会被\((x/gcd,y/gcd)\)挡住。

特殊情况:除了\((0,0)\)以外不存在\(x=y\)的了,除了\((0,1),(1,0)\)以外不存在\(x=0\)\(y=0\)的。

2、\(3\)个特殊的拿走后,做一下对称,答案是满足\((x\geq 1,y>1,gcd(x,y)=1,x<y)\)的数量\(*2\)\(+3\),这就是\(\varphi(2)+\varphi(3)+...\varphi(n)\)

3、\(n\)很小,暴力求欧拉函数也可以

\(1\sim n\)把包含\(p1\)的删掉

\(n*(1-1/p1)\)

再把含p2的删掉

\(n*(1-1/p1)*(1-1/p2)\)

再把含\(p3\)的删掉

\(n*(1-1/p1)*(1-1/p2)*(1-1/p3)\)

...

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010,mod=998244353;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
int n,ans;
int phi(int x)
{
	int res=x;
	for(int i=2;i<=x/i;i++)
		if(x%i==0)
		{
			res=res/i*(i-1);
			while(x%i==0)
				x=x/i;
		}
	if(x!=1)
		res=res/x*(x-1); 
	return res;
}
int main()
{
	freopen("queue.in","r",stdin);
	freopen("queue.out","w",stdout);
	n=read()-1;
	for(int i=2;i<=n;i++)
		ans=ans+phi(i);
	ans=ans*2+3;
	cout<<ans;
}

BZOJ2705 洛谷P2303 Longge的问题

\(gcd(i,n)\)\(gcd\)结果一定是\(n\)的因子。

如果固定\(gcd\)\(d\)\(d\)的贡献是\(d*\)有几个数字\(i\)\(n\)\(gcd\)恰好为\(d\)

拿着\(gcd(i,n)=d,1\leq i \leq n\)开始思考:同时除以\(d\),问题转变成\(gcd(i,n/d)=1,1\leq i \leq n/d\),这其实就是\(\varphi (n/d)\)

结论是暴力枚举因子\(d\),求\(\sum d*\varphi(n/d)\)

注意n需要开ll

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010,mod=998244353;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
ll n,ans;
ll phi(ll x)
{
    ll res=x;
    for(int i=2;i<=x/i;i++)
        if(x%i==0)
        {
            res=res/i*(i-1);
            while(x%i==0)
                x=x/i;
        }
    if(x!=1)
        res=res/x*(x-1); 
    return res;
}
int main()
{
    n=read();
    for(int d=1;d<=n/d;d++)
    {
        if(n%d)
            continue;
        ans=ans+1ll*d*phi(n/d);
        if(n/d!=d){
            ans=ans+1ll*n/d*phi(d);
        }
    }
    cout<<ans;
}

BZOJ3884. 上帝与集合的正确用法

\(ans=2^{2^{2^{2...}}}\)

则根据扩展欧拉定理,\(ans \mod p=2^{ans\mod \varphi(p)+\varphi (p)}\mod p\)

左边是要求的,右边只有\(ans \mod \varphi(p)\)不知道。递归地去求\(ans\mod \varphi(p)\),依然使用拓欧,直到模数为\(1\),此时可以直接返回\(0\)

在oj上跑了1.35s,在洛谷上跑了300ms,大胆推测oj是洛谷的四倍()

使用记忆化可以使得平均下降约200ms,但是我在oj上调大了时限,都能过。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010;//1e9+7;
ll read(){ll x;scanf("%lld",&x);return x;}
ll quick(ll a,ll b,ll mod){ll t=1;while(b){if(b&1)t=t*a%mod;b=b/2;a=a*a%mod;}return t;}
int lowbit(int x){return x&(-x);}
int cnt,pri[700000],v[10000010],phi[10000010];
int ask(int p)
{
    if(p==1)
        return 0;
    return quick(2,ask(phi[p])+phi[p],p);
}
void work()
{
    cout<<ask(read())<<'\n';
}
int main()
{
    // freopen("queue.in","r",stdin);
    // freopen("queue.out","w",stdout);
    phi[1]=1;
    for(int i=2;i<=10000000;i++)
    {
        if(v[i]==0){
            pri[++cnt]=i;
            v[i]=i;
            phi[i]=i-1;
        }
        for(int j=1;1ll*i*pri[j]<=10000000&&j<=cnt&&pri[j]<=v[i];j++)
        {
            v[i*pri[j]]=i;
            if(i%pri[j]==0)//有多个pri[j]
                phi[i*pri[j]]=phi[i]*pri[j];
            else
                phi[i*pri[j]]=phi[i]*(pri[j]-1);
        }
    }
    for(int t=read();t;t--)
        work();
}

BZOJ2186 [Sdoi2008] 沙拉公主的困惑

\(1\sim n!\)里和\(m!\)互质的数量。

由于\(gcd(i,m!)=gcd(i-m!,m!)\),因此把\(1\sim n!\)分成\(n!/m!\)份,每一份的互质情况都一样,求\(1\sim m!\)里有几个和\(m!\)互质,最后乘一下就好了。

\(1\sim m!\)里有几个和\(m!\)互质,这就是欧拉函数的定义,根据\(\varphi(n)=n\prod{(p_i-1)\over p_i}\),这里的\(p_i\)包含了\(1\sim m\)里的所有质数。由于多组询问,可以把这个\(\prod\)前缀预处理一下。

20250812更新:类似于组合数的大\(n,m\)小模数,本题如果模数过小也会遭遇一些问题,比如\(n!/m!*phi(m!)\)虽然\(\%mod\)不是\(0\),但是\(n!\)\(m!\)\(\varphi(m!)\)可能有多个\(mod\),为\(0\)了。我们掏出拓展卢卡斯的做法,对\(n!\)\(\varphi(i)\)都处理一下包含了几个\(mod\),把\(mod\)的数量统计一下,和\(mod\)互质的部分乘在一起。这样输出的时候判断一下\(mod\)的数量是不是\(0\)就可以知道答案是不是\(0\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10000010;
int phi[N],f[N],mod=998244353;
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;a=a*a%mod;b=b/2;}return t;}
int lowbit(int x){return x&(-x);}
ll read(){ll x;scanf("%lld",&x);return x;}
int pri[N],v[N],cnt,numphi[N],numf[N];
int main()
{
//	 freopen("1.in","r",stdin);
	// freopen(".out","w",stdout);
	int t=read();
	mod=read();

    for(int i=2;i<=10000000;i++)
    {
        if(v[i]==0){
            pri[++cnt]=i;
            v[i]=i;
        }
        for(int j=1;1ll*i*pri[j]<=10000000&&j<=cnt&&pri[j]<=v[i];j++)
            v[i*pri[j]]=i;
    }
    phi[0]=phi[1]=f[0]=f[1]=1;
    for(int i=2;i<=10000000;i++)
    {
    	if(v[i]==i){
    		int t=(i-1);
			while(t%mod==0)t=t/mod,numphi[i]++;
    		phi[i]=1ll*phi[i-1]*t%mod;
    		numphi[i]+=numphi[i-1];//numphi[i]表示phi[i]
			//里面mod的数量 
    	}
    	else{
    		int t=i;
			while(t%mod==0)t=t/mod,numphi[i]++;
    		phi[i]=1ll*phi[i-1]*t%mod;//phi[i!]
    		numphi[i]+=numphi[i-1];
    	}
		int t=i;
		while(t%mod==0)t=t/mod,numf[i]++;
    	f[i]=1ll*f[i-1]*t%mod;//预处理阶乘
    	numf[i]+=numf[i-1];
    }
	for(;t;t--)
	{
		int n=read(),m=read();
		if(numf[n]-numf[m]+numphi[m]>0)
			cout<<"0\n";
		else
			cout<<f[n]*quick(f[m],mod-2)%mod*phi[m]%mod<<'\n';
	}
}

BZOJ2749 [HAOI2012] 洛谷P2350 外星人

给你一个\(N\),由于\(N\)很大,以唯一分解的形式给你。

\(\varphi^x(N)\)表示\(\varphi(\varphi(\varphi(...\varphi(N))\),迭代\(x\)次。

手玩几个可以发现2很重要

如果刚开始是\(3^3\)

那么下一次\(2^1*3^2\)

下一次\(3^1*2^1\)

下一次\(2^1\)

还需要一次

如果刚开始是\(5^3\)

下一次\(2^2*5^2\)

下一次\(2^3*5^1\)

下一次\(2^4\)

还需要四次

一个\(5\)=两个\(2\),因此答案是\(2*3+1\)

如果3和5同时来呢

答案是\(1+q[3]+q[5]*2\)其中\(q[i]\)表示\(i\)的幂次而不是题意的\(q[i]\)

如果2和3和5同时来呢

答案是\(q[2]+q[3]+q[5]*2\)

这个系数到底是啥呢,比如\(11\)会变成\(10=2*5\)\(2\)\(5\)分开计算,分别会贡献\(1\)\(2\)\(2\)\(2\)。设\(f[i]\)表示\(i\)\(2\)的贡献,如果i是质数,\(f[i]=f[i-1]\),否则\(f[i]=\sum f[p_j]*k_j\)其中\(p_j\)\(k_j\)是i的质因数分解后的结果。可以发现你任选一个\(p_j\)\(f[i]\)也等于\(f[i/p_j]+f[p_j]\)这个可以线性筛的时候计算一下。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10000010;
ll mod=998244353;
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;a=a*a%mod;b=b/2;}return t;}
int lowbit(int x){return x&(-x);}
ll read(){ll x;scanf("%lld",&x);return x;}
int pri[N],v[N],f[N],cnt;
int main()
{
	// freopen(".in","r",stdin);
	// freopen(".out","w",stdout);
	f[1]=1;
    for(int i=2;i<=100000;i++)
    {
        if(v[i]==0){
            pri[++cnt]=i;
            v[i]=i;
            f[i]=f[i-1];
        }
        for(int j=1;1ll*i*pri[j]<=100000&&j<=cnt&&pri[j]<=v[i];j++){
            v[i*pri[j]]=i;
            f[i*pri[j]]=f[i]+f[pri[j]];
        }
    }
	for(int t=read();t;t--)
	{
		ll ans=1;//假装没有2
		for(int n=read();n;n--)
		{
			int p=read();
			if(p==2)//发现有2,此时让ans--
				ans--;
			ans=ans+f[p]*read();
		}
		cout<<ans<<'\n';
	}
}

BZOJ1876 [SDOI2009] SuperGCD

高精度,求gcd

python秒了(bushi)

import math
import sys
sys.set_int_max_str_digits(10010)
a=int(input())
b=int(input())
print(math.gcd(a,b))

使用辗转相除法很不妙,一个是容易超时,一个是高精度除法不好写。

使用普通更相减损术,复杂度可能是\(O(n)\)的,比如一个大奇数和一个小\(2\),一直减一直减...

考虑奇偶加速的更相减损术:如果\(ab\)都是偶数,可以同时除以二,令计数器\(cnt++\)。如果其中一个是偶数,那么可以把他除以二。如果两个都是奇数,则可以大的\(-=\)小的。

普通高精度复杂度吃满了\(O(n*log_2(10^n))\approx 3n^2\),本地跑第四个测试点需要\(3.5s\),我写了个压位高精度才能通过。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10000010;
ll mod=998244353;
ll quick(ll a,ll b){ll t=1;while(b){if(b&1)t=t*a%mod;a=a*a%mod;b=b/2;}return t;}
int lowbit(int x){return x&(-x);}
ll read(){ll x;scanf("%lld",&x);return x;}
void input(vector<int>&a)
{
	string s;
	cin>>s;
	reverse(s.begin(),s.end());
	for(int i=0;i*4<s.size();i++)
	{
		a.push_back(0);
		for(int j=min(i*4+3,(int)s.size()-1);j>=i*4;j--)
			a.back()=a.back()*10+s[j]-'0';
	}
}
bool operator <(vector<int>&a,vector<int>&b)
{
	if(a.size()<b.size())
		return 1;
	if(a.size()>b.size())
		return 0;
	for(int i=a.size()-1;i>=0;i--)
	{
		if(a[i]<b[i])
			return 1;
		if(a[i]>b[i])
			return 0;
	}
	return 0;//相等return 0
}
void operator -=(vector<int>&a,vector<int>&b)
{
	for(int i=0;i<b.size();i++)
		a[i]-=b[i];
	for(int i=0;i<a.size();i++)
		if(a[i]<0){
			a[i+1]--;
			a[i]+=10000;
		}
	while(a.size()&&a.back()==0)
		a.pop_back();
}
void operator /=(vector<int>&a,int v)
{
	for(int i=a.size()-1;i>=0;i--)
	{
		if(a[i]%2==0)
			a[i]=a[i]/2;
		else
			a[i]=a[i]/2,a[i-1]+=10000;
	}
	if(a.back()==0)
		a.pop_back();
}
void operator *=(vector<int>&a,int v)
{
	a.push_back(0);
	for(int i=0;i<a.size();i++)
		a[i]=a[i]*2;
	for(int i=0;i<a.size();i++)
		if(a[i]>=10000)
		{
			a[i]-=10000;
			a[i+1]++;
		}
	if(a.back()==0)
		a.pop_back();
}
int main()
{
	// freopen(".in","r",stdin);
	// freopen(".out","w",stdout);
	vector<int>a,b;
	input(a);
	input(b);
	int cnt=0,num=0;
	while(a.size()!=0&&b.size()!=0)
	{
		if(a<b)
			swap(a,b);
		num++;
		if(a[0]%2==0&&b[0]%2==0)
		{
			cnt++;
			a/=2;
			b/=2;
		}
		else if(a[0]%2==0){
			a/=2;
		}
		else if(b[0]%2==0)
			b/=2;
		else
			a-=b;
	}
	if(a.size()==0)
		a=b;
	for(int i=1;i<=cnt;i++)
		a*=2;
	reverse(a.begin(),a.end());
	cout<<a[0];
	for(int i=1;i<a.size();i++)
		printf("%04d",a[i]);
}

洛谷P2480 古代猪文

样例是\(2^{C(4,1)+C(4,2)+C(4,4)}=2^{11}=2048\)

模数\(999911659\)是个质数,根据扩展欧拉定理,我们求$$sum=\sum_{d|n} C(n,d)\ mod\ 999911658 $$,再输出\(p^{sum} \% 999911659\)即可

\(999911658=2*3*4679*35617\),质因数幂次都是\(1\),可以不用拓展卢卡斯定理来做。可以对于每个\(C(n,d)\)手动求出对四个质数的余数,用中国剩余定理合并起来。

以模\(4679\)意义下的组合数为例,是不能用阶乘和阶乘的逆元来求的,你需要使用卢卡斯定理处理之。

需要特判\(g=mod\)的情况。因为如果恰好\(mod\)也是\(0\)的话,根据我们的拓展欧拉定理,应该输出\(p^{sum+\varphi(999911659)}=0\),不写的话是\(p^0\)为1。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick(ll a,ll b,int mod=999911659){ll t=1;while(b){if(b&1)t=t*a%mod;a=a*a%mod;b=b/2;}return t;}
const int N=10000010,mod=999911659;
int lowbit(int x){return x&(-x);}
ll read(){ll x;scanf("%lld",&x);return x;}
int m[5],lx[5][36000],inv[5][36000],a[5],M[5],t[5],n,g,sum;
int lucas(int a,int b,int i)
{
	if(a<b)
		return 0;	
	if(a<m[i]&&b<m[i])
		return lx[i][a]*inv[i][b]%m[i]*inv[i][a-b]%m[i];
	return lucas(a%m[i],b%m[i],i)*lucas(a/m[i],b/m[i],i)%m[i];
}
int ask(int k)
{
	int x=0;
	for(int i=1;i<=4;i++){
		a[i]=lucas(n,k,i);
		// printf("C(%d,%d)%%%d=%d\n",n,k,m[i],lucas(n,k,i));
		x=(x+1ll*a[i]*M[i]%(mod-1)*t[i])%(mod-1);
	}
	return x;
}
int main()
{
	// freopen(".in","r",stdin);
	// freopen(".out","w",stdout);
	m[1]=2;
	m[2]=3;
	m[3]=4679;
	m[4]=35617;
	for(int i=1;i<=4;i++)
	{
		M[i]=(mod-1)/m[i];
		for(t[i]=1;t[i]*M[i]%m[i]!=1;t[i]++)
			;
	}
	for(int i=1;i<=4;i++)
	{
		lx[i][0]=inv[i][0]=1;
		for(int j=1;j<m[i];j++){
			lx[i][j]=lx[i][j-1]*j%m[i];
			inv[i][j]=quick(lx[i][j],m[i]-2,m[i]);
		}
	}
	n=read(),g=read();
	for(int k=1;k<=n/k;k++)
	{
		if(n%k)
			continue;
		sum=(sum+ask(k))%(mod-1);
		if(n/k!=k)
			sum=(sum+ask(n/k))%(mod-1);
	}
	if(g==mod)
		cout<<0;
	else
		cout<<quick(g,sum);
}

我们C班数论进阶再见!ヾ( ̄▽ ̄)ByeBye

posted @ 2025-08-04 21:51  zzuqy  阅读(116)  评论(0)    收藏  举报