2024上海大学生程序设计竞赛I-六元组计数原根知识详解

以前基本没有了解原根相关的一块内容,最近正好碰到了这个题,于是写一篇博客记录一下。

在这里插入图片描述

这道题的总体思路就是比较明显,就是先算出 a p = x a^p=x ap=x对于每个 x x x的解的个数,然后NTT算一下即可。

先来讲一下怎么求欧拉函数 ϕ ( x ) \phi (x) ϕ(x),即1到 x − 1 x-1 x1中与 x x x互素的数的个数,我们一般在线性筛里面通过递推来求出这个函数。也就是我们要找出 ϕ ( i ∗ p [ j ] ) \phi(i*p[j]) ϕ(ip[j]) ϕ ( i ) , ϕ ( p [ j ] ) , i , p [ j ] \phi (i),\phi(p[j]),i,p[j] ϕ(i),ϕ(p[j]),i,p[j]之间的关系。我们把与 ϕ ( i ) \phi(i) ϕ(i)所代表的集合写在一行里面,然后第二行写上前一行的数加上 i i i,一直写 p [ j ] p[j] p[j]行。

现在举两个例子。

例1:

i%p[j]!=0
p[j]=3,i=8//虽然实际不会出现这种,但是对于证明来讲问题不大
1  3  5  7
9  11 13 15
17 19 21 23然后phi[24]表示的集合为
1       5   711  13
17 19       23  

例2:

i%p[j]==0
p[j]=2,i=10
1  3  7  9
11 13 17 19
phi[i*p[j]]=8

在例1中,因为 i m o d p [ j ] ≠ 0 i \mod p[j] \ne 0 imodp[j]=0,也就是 ϕ ( i ) \phi(i) ϕ(i)所表示的集合中存在 p [ j ] p[j] p[j]的倍数。然后你可以发现,每一列里面恰好就有一个是 p [ j ] p[j] p[j]的倍数,我们不妨设某一列第一个数为 x x x,且 y = x m o d p [ j ] y=x\mod p[j] y=xmodp[j],那么因为 p [ j ] p[j] p[j]为质数,所以 y m o d p [ j ] , y + i m o d p [ j ] , y + 2 i m o d p [ j ] , . . . , y + ( p [ j ] − 1 ) i m o d p [ j ] y\mod p[j],y+i\mod p[j],y+2i\mod p[j],...,y+(p[j]-1)i\mod p[j] ymodp[j],y+imodp[j],y+2imodp[j],...,y+(p[j]1)imodp[j] 0 , 1 , . . . , p [ j ] − 1 0,1,...,p[j]-1 0,1,...,p[j]1恰好每个一个,因此可以知道 p h i [ i ∗ p [ j ] ] = p h i [ i ] ∗ ( p [ j ] − 1 ) ( i m o d p [ j ] ≠ 0 ) phi[i*p[j]]=phi[i]*(p[j]-1)(i \mod p[j] \ne 0) phi[ip[j]]=phi[i](p[j]1)(imodp[j]=0)

在例2中,因为 i m o d p [ j ] = 0 i\mod p[j] =0 imodp[j]=0,因此 ϕ ( i ) \phi(i) ϕ(i)所表示的集合里面没有 p [ j ] p[j] p[j]的倍数,因此第一行里面没有被删掉的,因为相邻两行之间的差都是 p [ j ] p[j] p[j]的倍数,所以不会有任何的数为 p [ j ] p[j] p[j]的倍数。

然后就是原根部分的知识。

如果 x x x是模 P P P意义下的原根,那么 1 , 2 , . . . , P − 1 1,2,...,P-1 1,2,...,P1中每个数都能写成 x t ( 0 < = t < = P − 2 ) x^t(0<=t<=P-2) xt(0<=t<=P2)的形式,把0的特殊情况处理掉,然后我们就可以把乘法转换成加法,也就是求 i ∗ j = x m o d ( P − 1 ) ( 0 < = i < = P − 2 , 1 < = j < = N ) i*j=x\mod (P-1)(0<=i<=P-2,1<=j<=N) ij=xmod(P1)(0<=i<=P2,1<=j<=N)每个 x x x的解。

到这一步以后我就卡了很久,因为不知道怎么进一步去求了,虽然和 P − 1 P-1 P1公因数相同的数会产生一样的循环节,但是如果 N N N不是 P − 1 P-1 P1的倍数就无从下手了,因此我感觉自己还有什么性质没有发现,然后我就不停的打表观察。

直到我打表直接求出每个 x x x的解的时候,我发现无论 N N N怎么取,和 P − 1 P-1 P1公因数相同的数的解的个数是一样的,那么我们就可以枚举 P − 1 P-1 P1的每个因子,然后暴力枚举每个 i i i有多少个解,这样时间复杂度是 O ( P P ) O(P\sqrt P) O(PP ),理论上是可以过的,虽然有点不太优秀。然后我就开始思考其背后的原因。

假设 N N N是不断增大的, N N N每增大 1 1 1,这个结果都能保持不变,也就是说对于任意一个 j j j, i ∗ j ( 0 < = i < = P − 2 ) i*j(0<=i<=P-2) ij(0<=i<=P2)都能满足和 P − 1 P-1 P1公因数相同的数出现个数一样(固定 j j j,变化 i i i)。

然后我们尝试理解一组数乘完某个数以后仍然在同一组。显然 j = 1 j=1 j=1时,即 0 , 1 , 2 , . . . , P − 2 0,1,2,...,P-2 0,1,2,...,P2种显然满足这个条件,那么我们把相同公因数的数放到同一组里面去,考虑一组里面的数同时乘上同一个数会怎么样。结论是要么这个公因数保持不变,要么翻倍。不妨设某组数和 P − 1 P-1 P1的最大公因数为 d d d,这一组里面某个数为 a d ad ad,然后乘以一个 k k k。那么 g c d ( k a d m o d P − 1 , P − 1 ) gcd(kad\mod P-1,P-1) gcd(kadmodP1,P1)就为 g c d ( k a d , P − 1 ) gcd(kad,P-1) gcd(kad,P1),这就是辗转相除法,因为 g c d ( a , P − 1 ) = 1 gcd(a,P-1)=1 gcd(a,P1)=1,所以 g c d ( k a d , P − 1 ) = g c d ( k d , P − 1 ) gcd(kad,P-1)=gcd(kd,P-1) gcd(kad,P1)=gcd(kd,P1)。特别地,如果 g c d ( k a d , P − 1 ) = P − 1 gcd(kad,P-1)=P-1 gcd(kad,P1)=P1,结果就是0。

然后我们还要弄明白一点,就是同一组数乘完某个数以后虽然仍然在同一组,那么是不是那一组里面每个数的个数都是相同的,答案是肯定的。首先证明,对于全部与 P − 1 P-1 P1互素的数,如果同时乘上一个与 P − 1 P-1 P1互素的数,那么乘完以后还是原来的那些数,只是顺序发生了改变。设原来两个数为 x , y ( x ≠ y ) ,那么 k x − k y = k ( x − y ) ≠ 0 ( m o d P − 1 ) x,y(x\ne y),那么kx-ky=k(x-y) \ne 0(mod\ P-1) x,y(x=y),那么kxky=k(xy)=0(mod P1),也就是两两不同。然后我们先考虑 g c d ( x , P − 1 ) gcd(x,P-1) gcd(x,P1) 1 1 1的组,并且乘的 k k k P − 1 P-1 P1的因子的情况,然后就可以根据刚刚证明的性质把全部情况转换成这种情况,也就是先要提最大公因数,转换成互质,然后把乘的数拆成两部分,一部分互质,一部分是因子,前一部分不会改变数的值,只会改变顺序,后一部分就是特殊的情况。现在来考虑这种特殊情况,再次强调 k ∣ P − 1 k|P-1 kP1,那么我们令 t = P − 1 k t=\frac{P-1}{k} t=kP1,如果两个数 x , y x,y x,y(其中 g c d ( x y , P − 1 ) = 1 ) gcd(xy,P-1)=1) gcd(xy,P1)=1)满足 k x = k y ( m o d P − 1 ) kx=ky(mod\ P-1) kx=ky(mod P1),那么有 x = y ( m o d t ) x=y(mod\ t) x=y(mod t),这个问题就能转化成,所有与 P − 1 P-1 P1互素的数在模 t t t意义下是不是每个数出现次数相同。想证明这个可以用到前面求欧拉函数的方法,我们不妨考虑和 t t t互素的每个数对应多少个和 P − 1 P-1 P1互素的个数。因为 k = P − 1 t k=\frac{P-1}{t} k=tP1,然后我们就可以把 k k k拆成若干个质数相乘,每乘一个质数就用一次求欧拉函数的方法(同一列都是对应同一个与 t t t互素的数),就能知道每个与 t t t互素的数在每次操作以后对应的数的个数相同,这样就能证明这个性质了。

在具体实现的时候,我采用了先枚举每个 P − 1 P-1 P1的因子 i i i,然后遍历整个周期 p e r i o d = P − 1 i period=\frac{P-1}{i} period=iP1,这样的复杂度是小于 p l o g p \sqrt plogp p logp的(但是要先预处理出每个数和 P − 1 P-1 P1的最大公因数,否则要多一个log)。因为这是全部因子之和,而我们知道因子和的计算方式为 ( 1 + p 1 + p 1 2 + . . . + p 1 c 1 ) ( 1 + p 2 + p 2 2 + . . . + p 2 c 2 ) . . . ( 1 + p n + p n 2 + . . . + p n c n ) = ∏ i = 1 n p i c i + 1 − 1 p i − 1 < = ∏ i = 1 n p i c i + 1 p i − 1 = ∏ i = 1 n p i c i ∏ i = 1 n p i p i − 1 < = ( P − 1 ) ∏ i = 1 n i + 1 i = ( n + 1 ) ( P − 1 ) < = ( P − 1 ) l o g ( P − 1 ) (1+p_1+p_1^2+...+p_1^{c_1})(1+p_2+p_2^2+...+p_2^{c_2})...(1+p_n+p_n^2+...+p_n^{c_n})=\prod_{i=1}^n\frac{p_i^{c_i+1}-1}{p_i-1}<=\prod_{i=1}^n\frac{p_i^{c_i+1}}{p_i-1}=\prod_{i=1}^np_i^{c_i}\prod_{i=1}^n\frac{p_i}{p_i-1}<=(P-1)\prod_{i=1}^n\frac{i+1}{i}=(n+1)(P-1)<=(P-1)log(P-1) (1+p1+p12+...+p1c1)(1+p2+p22+...+p2c2)...(1+pn+pn2+...+pncn)=i=1npi1pici+11<=i=1npi1pici+1=i=1npicii=1npi1pi<=(P1)i=1nii+1=(n+1)(P1)<=(P1)log(P1)

然后我们可以找出一个原根,算出每个数是原根的几次方,然后就能得到每个数出现的次数。然后用ntt卷积一次即可。

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define dwn(i,x,y) for(int i=x;i>=y;i--)
#define ll long long
using namespace std;
template<typename T>inline void qr(T &x){x=0;int f=0;char s=getchar();while(!isdigit(s))f|=s=='-',s=getchar();while(isdigit(s))x=x*10+s-48,s=getchar();x=f?-x:x;
}
int cc=0,buf[31];
template<typename T>inline void qw(T x){if(x<0)putchar('-'),x=-x;do{buf[++cc]=int(x%10);x/=10;}while(x);while(cc)putchar(buf[cc--]+'0');
}
const int N=1e5+10,mod=998244353;
namespace NTT{const int G=3,Gi=332748118;int n,m;int lim=1,cnt;int pos[N<<2];int a[N<<2],b[N<<2];int power(int x,int y){int ret=1;while(y){if(y&1)ret=1ll*ret*x%mod;x=1ll*x*x%mod;y>>=1;}return ret;}void ntt(int *A,int type){rep(i,0,lim-1)if(i<pos[i])swap(A[i],A[pos[i]]);for(int mid=1;mid<lim;mid<<=1){int Wn=power(type==1?G:Gi,(mod-1)/(mid<<1));for(int R=mid<<1,j=0;j<lim;j+=R){int w=1;for(int k=0;k<mid;k++,w=1ll*w*Wn%mod){int x=A[j+k],y=1ll*w*A[j+mid+k]%mod;A[j+k]=(x+y)%mod;A[j+k+mid]=(x-y+mod)%mod;}}}}void work(){while(lim<=n+m)lim<<=1,cnt++;rep(i,0,lim-1)pos[i]=(pos[i>>1]>>1)|((i&1)<<(cnt-1));ntt(a,1),ntt(b,1);rep(i,0,lim)a[i]=1ll*a[i]*b[i]%mod;ntt(a,-1);int inv=power(lim,mod-2);rep(i,0,n+m)a[i]=1ll*a[i]*inv%mod;}
}
int P,n;
int cnt,p[N],phi[N];bool v[N];
int sum[N],s[N];
int pre_gcd[N];
int pos[N];
int num[N];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
void solve(){qr(P),qr(n);rep(i,2,100000){if(!v[i])p[++cnt]=i,phi[i]=i-1;for(int j=1;j<=cnt&&i*p[j]<=100000;j++){v[i*p[j]]=1;if(i%p[j]==0){phi[i*p[j]]=phi[i]*p[j];break;}else phi[i*p[j]]=phi[i]*(p[j]-1);}}rep(i,2,P-1){int now=1;bool bk=1;rep(j,1,P-1){now=1ll*now*i%P;if(now==1&&j!=P-1){bk=0;break;}pos[now]=j;}if(bk)break;}rep(i,1,P-2){if(gcd(i,P-1)==1)s[i]++;s[i]+=s[i-1];}rep(i,0,P-1)pre_gcd[i]=gcd(i,P-1);sum[P-1]=n%mod;//gcd(x,P-1)=irep(i,1,P-2)if((P-1)%i==0){int period=(P-1)/i;int group_siz=phi[(P-1)/i];rep(j,1,period){if(j==period){(sum[P-1]+=1ll*group_siz*(n/period)%mod)%=mod;break;}int now=pre_gcd[i*j];int p1=n/period%mod,p2=n%period;int group_siz2=phi[(P-1)/now];(sum[now]+=1ll*(group_siz/group_siz2)*(p1+(j<=p2))%mod)%=mod;}}num[0]=n%mod;rep(i,1,P-1)num[i]=sum[gcd(pos[i],P-1)];NTT::n=NTT::m=P-1;rep(i,0,P-1)NTT::a[i]=NTT::b[i]=num[i];NTT::work();int ans=0;rep(i,0,2*(P-1))(ans+=1ll*NTT::a[i]*num[i%P]%mod)%=mod;qw(ans);
}
int main(){int tt;tt=1;while(tt--)solve();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/39675.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端FCP指标优化

优化前 第三方依赖按需引入之后&#xff0c;打包的总体积减小到初始值的55%&#xff0c;但是依然存在很大的js文件&#xff0c;需要继续优化 chunk-vendors.js进行分包之后 截图 compression-webpack-plugin压缩之后 截图

大学新生人工智能学习路线规划

1. 引言 七月来临&#xff0c;各省高考分数已揭榜完成。而高考的完结并不意味着学习的结束&#xff0c;而是新旅程的开始。对于有志于踏入IT领域的高考少年们&#xff0c;这个假期是开启探索IT世界的绝佳时机。作为该领域的前行者和经验前辈&#xff0c;我愿意为准新生们提供一…

剪映 v5.5 Pro Vip解锁版:使用指南与注意事项

摘要&#xff1a;本文介绍了剪映Pro VIP解锁版的使用方法&#xff0c;包括安装、测试和使用VIP素材的步骤&#xff0c;以及如何避免误报和保持解锁状态的建议。 正文&#xff1a; 剪映Pro是一款广受欢迎的视频编辑软件&#xff0c;提供了丰富的视频编辑功能和大量高质量的素材…

发送微信消息和文件

参考&#xff1a;https://www.bilibili.com/video/BV1S84y1m7xd 安装&#xff1a; pip install PyOfficeRobotimport PyOfficeRobotPyOfficeRobot.chat.send_message(who"文件传输助手", message"你好&#xff0c;我是PyOfficeRobot&#xff0c;有什么可以帮助…

RabbitMQ中java实现队列和交换机的声明

java实现队列和交换机的声明 在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时&#xff0c;队列和交换机是程序员定义的&#xff0c;将来项目上线&#xff0c;又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来&#xff0c;…

用MySQL+node+vue做一个学生信息管理系统(一):配置项目

先用npm init -y生成配置文件 在项目下新建src文件夹&#xff0c;app.js文件。src目录用来放静态资源文件&#xff0c;app.js是服务器文件&#xff0c;index.js是vue的入口文件 使用npm install express下载express框架 在app.js文件夹开启node服务&#xff0c;监听的端口为…

Go语言--运算符

算术运算符 关系运算符 不能写0<a<10&#xff0c;要判断必须0<a&&a<10。因为int和bool不兼容 逻辑运算符 位运算符 赋值运算符 其他 运算符的优先级

国家海岸线变化评估:新英格兰和中大西洋沿岸海岸线的历史变化

National Assessment of Shoreline Change: Historical Shoreline Change along the New England and Mid-Atlantic Coasts 国家海岸线变化评估&#xff1a;新英格兰和中大西洋沿岸海岸线的历史变化 摘要 海滩侵蚀是美国许多公海沿岸的一个长期问题。随着沿岸人口的不断增加…

永辉超市购物卡有什么用?

感觉现在在超市买东西&#xff0c;还不如网购 这不&#xff0c;端午的时候&#xff0c;朋友送的永辉卡&#xff0c;一直没时间去用&#xff0c;我总担心过期 但是去了超市后&#xff0c;又不知道买什么&#xff0c;最后空手而归 还好收卡云可以回收永辉卡&#xff0c;两张三…

《C++20设计模式》适配器模式经验分享

文章目录 一、前言二、对于接口的讨论三、实现1、对象适配器1.1 UML类图1.2 实现 2、类适配器 四、最后 一、前言 从适配器模式开始就是类的组合聚合&#xff0c;类与类之间结构性的问题了。 适配器模式解决的问题&#xff1a; 适配器模式能够在不破坏现有系统结构的情况下&a…

mapreduce实现bean的序列化与反序列化

目录 序列化&#xff08;Serialization&#xff09; 反序列化&#xff08;Deserialization&#xff09; 事例操作 UserSale 重写序列化方法 重写反序列化 重写toString方法 SaleMapper SaleReducer SaleDriver 序列化&#xff08;Serialization&#xff09; 序列化是将…

【后端面试题】【中间件】【NoSQL】MongoDB的配置服务器、复制机制、写入语义和面试准备

MongoDB的配置服务器 引入了分片机制之后&#xff0c;MongoDB启用了配置服务器(config server) 来存储元数据&#xff0c;这些元数据包括分片信息、权限控制信息&#xff0c;用来控制分布式锁。其中分片信息还会被负责执行查询mongos使用。 MongoDB的配置服务器有一个很大的优…

WPF----自定义滚动条ScrollViewer

滚动条是项目当中经常用到的一个控件&#xff0c;大部分对外项目都有外观的需求&#xff0c;因此需要自定义&#xff0c;文中主要是针对一段动态的状态数据进行展示&#xff0c;并保证数据始终在最新一条&#xff0c;就是需要滚动条滚动到底部。 1&#xff0c;xaml中引入 <…

zxing-cpp+OpenCV根据字符串生成条形码

编译构建 需要使用到 CMake、Git、GCC 或 MSVC。 github 链接&#xff1a;https://github.com/zxing-cpp/zxing-cpp 编译之前请确保&#xff1a; 确保安装了 CMake 版本 3.15 或更高版本。 确保安装了与 C17 兼容的编译器(最低VS 2019 16.8 / gcc 7 / clang 5)。 编译构建…

Python面试宝典第4题:环形链表

题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。如果存在环 &#xff0c;则返回 true 。 否则&#xff0c;返回 false 。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xf…

AI智能体|AI打工我躺平!使用扣子Coze智能体自动生成和发布文章到微信公众号(一)

大家好&#xff0c;我是无界生长&#xff0c;国内最大AI付费社群“AI破局俱乐部”初创合伙人。这是我的第 44 篇原创文章——《AI智能体&#xff5c;AI打工我躺平&#xff01;使用扣子Coze智能体自动生成和发布文章到微信公众号&#xff08;一&#xff09;》 AI智能体&#xf…

《涅朵奇卡:一个女人的一生》读后感

这周的计划是看完海明威的《丧钟为谁而鸣》&#xff0c;但是因为下班晚&#xff0c;而且书的体量大&#xff0c;所以只看了一半。本来以为这周的阅读计划完不成了&#xff0c;不料昨天加完班后拿起新到的《涅朵奇卡&#xff1a;一个女人的一生》&#xff0c;不自觉就陷进去了&a…

端口聚合基础知识

一、什么是端口聚合 端口聚合是将多个物理端口捆绑在一起&#xff0c;形成一个逻辑链路&#xff0c;以实现带宽增加、提高冗余和负载均衡的技术。端口聚合&#xff0c;也称为以太通道&#xff08;Ethernet Channel&#xff09;&#xff0c;主要用于交换机之间的连接。在具有多…

开发数字药店APP实战:互联网医院系统源码详解

本篇文章&#xff0c;笔者将深入探讨如何开发一个功能完善的数字药店APP&#xff0c;并详细解析互联网医院系统的源码实现。 一、数字药店APP的需求分析 应具备以下基本功能&#xff1a; 用户注册与登录 药品搜索与浏览 在线下单与支付 订单管理 健康咨询与远程医疗 个人…

partition()方法——分割字符串为元组

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 partition()方法根据指定的分隔符将字符串进行分割。如果字符串中包含指定的分隔符&#xff0c;则返回一个3元的元组&#xff0c;第一个为…