题解 P5572 【[CmdOI2019]简单的数论题】
command_block · · 题解
官方题解 : 简单的数论题
打出
-
\Large 40'
打个答案的表就好了,
#include<algorithm>
#include<cstdio>
#define MaxN 4000500
#define MaxNum 2010
#define mod 23333
using namespace std;
int T,N,M;
bool e[MaxN+500];
int tn,p[MaxN+500],phi[MaxN+500];
int gcd[MaxNum+50][MaxNum+50];
int ans[MaxNum+50][MaxNum+50];
int gcdpre(int a,int b)
{
if (!b)return a;
if (gcd[a][b])return gcd[a][b];
return gcd[a][b]=gcdpre(b,a%b);
}
inline void getsth()
{
e[1]=1;phi[1]=1;
for (int i=2,t;i<=MaxN;i++){
if (!e[i]){
p[++tn]=i;
phi[i]=i-1;
}
for (int j=1;j<=tn&&(t=p[j]*i)<=MaxN;j++){
phi[t]=phi[i]*(i%p[j]==0 ? p[j] : p[j]-1);
e[t]=1;
if (i%p[j]==0)break;
}
}
for (int i=1;i<=MaxNum;i++)
for (int j=1;j<=MaxNum;j++)
gcd[i][j]=gcdpre(i,j);
for (int i=1;i<=MaxNum;i++)
for (int j=1;j<=MaxNum;j++)
ans[i][j]=(phi[i*j/gcd[i][j]/gcd[i][j]]
+ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1])%mod;
}
int main()
{
scanf("%d",&T);
getsth();
while(T--){
scanf("%d%d",&N,&M);
printf("%d\n",(ans[N][M]+mod)%mod);
}return 0;
}
-
\Large 60'
送分结束,开始推导式子:
求
- 设
G(n,k)=\sum\limits_{i=1}^n\varphi(ik)
.
设
不难发现,我们调用的
不过,由于
- 设
S(n,m,k)=\mu(k)G(n,k)G(m,k)
则
对
我们预处理出
令
设
当
当
直接分块套分块计算,复杂度为
总复杂度
#include<algorithm>
#include<cstdio>
#define ll long long
#define MaxN 30500
using namespace std;
const int mod=23333,B=200,lim=30000;
int tn,p[MaxN>>2],mu[MaxN],phi[MaxN]
,*G[MaxN],_G[MaxN*16],*gp=_G
,*S[B+5][B+5],_S[MaxN*(B+B/4)],*sp=_S;
void getsth()
{
phi[1]=mu[1]=1;
for (int i=2,t;i<=lim;i++){
if (!phi[i]){
mu[p[++tn]=i]=-1;
phi[i]=i-1;
}
for (int j=1;j<=tn&&(t=p[j]*i)<=lim;j++){
if (i%p[j]==0){
phi[t]=phi[i]*p[j];
break;
}mu[t]=-mu[i];
phi[t]=phi[i]*(p[j]-1);
}
}G[0]=gp;gp+=lim+1;
for (int x=1;x<=lim;x++){
G[x]=gp+1;
gp+=lim/x+1;
for (int y=1;x*y<=lim;y++)
G[x][y]=(G[x-1][y]+phi[x*y])%mod;
}
for (int j=1;j<=B;j++)
for (int i=j;i<=B;i++){
S[i][j]=sp;
sp+=lim/i+2;
for (int k=1;k*i<=lim;k++)
S[i][j][k]=(S[i][j][k-1]+mu[k]*G[i][k]%mod*G[j][k])%mod;
}
}
int f(int n,int m)
{
ll ans=0;
for (int i=1;i*B<=n;i++)
ans+=mu[i]*G[n/i][i]*G[m/i][i];
ans%=mod;
register int l=n/B+1,r;int tn,tm;
for (;l<=m;l=r+1){
r=min(n/(tn=n/l),m/(tm=m/l));
ans+=S[tn][tm][r]-S[tn][tm][l-1];
}return ans%mod;
}
int T,N,M;
int main()
{
scanf("%d",&T);
getsth();
while(T--){
scanf("%d%d",&N,&M);
if (N<M)swap(N,M);
ll ans=0;
int l=1,r;
for (;l<=M;l=r+1){
r=min(N/(N/l),M/(M/l));
ans+=f(N/l,M/l)*(r-l+1)%mod;
}printf("%d\n",(ans%mod+mod)%mod);
}return 0;
}
-
\Large \oplus\ 20'
发现预处理所有
(此外,
-
\Large 100'
承接上面的式子:
设
仍然不能整除分块,暴力的话需要
我们设
我们预处理
预处理时,对于
实际上还可以做到
对于
对于
综上,空间
常数比较小,所以比60pts做法快到不知哪里去了
#include<algorithm>
#include<cstdio>
#define ll long long
#define MaxN 50500
using namespace std;
const int mod=23333,B=150,lim=50000;
int tn,p[MaxN>>2],mu[MaxN],phi[MaxN]
,*G[MaxN],_G[MaxN*16],*gp=_G
,*R[B+5][B+5],_R[MaxN*(B+B/4)],*rp=_R;
void getsth()
{
phi[1]=mu[1]=1;
for (int i=2,t;i<=lim;i++){
if (!phi[i]){
mu[p[++tn]=i]=-1;
phi[i]=i-1;
}
for (int j=1;j<=tn&&(t=p[j]*i)<=lim;j++){
if (i%p[j]==0){
phi[t]=phi[i]*p[j];
break;
}mu[t]=-mu[i];
phi[t]=phi[i]*(p[j]-1);
}
}G[0]=gp;gp+=lim+1;
for (int x=1;x<=lim;x++){
G[x]=gp+1;
gp+=lim/x+1;
for (int y=1;x*y<=lim;y++)
G[x][y]=(G[x-1][y]+phi[x*y])%mod;
}
for (int j=1;j<=B;j++)
for (int i=j;i<=B;i++){
R[i][j]=rp;
rp+=lim/i+2;
int n0=lim/i;
for (int k=1;k<=n0;k++){
int sav=mu[k]*G[i][k]%mod*G[j][k];
for (int p=k;p<=n0;p+=k)
R[i][j][p]+=sav;
}
for (int k=1;k*i<=lim;k++)
R[i][j][k]=(R[i][j][k-1]+R[i][j][k])%mod;
}
}
int T,N,M;
int main()
{
scanf("%d",&T);
getsth();
while(T--){
scanf("%d%d",&N,&M);
long long ans=0;
for (int i=1;i*B<=N;i++)
for (int j=i;j*B<=N;j+=i)
ans+=mu[i]*G[N/j][i]*G[M/j][i];
ans%=mod;
register int l=N/B+1,r;int tn,tm;
for (;l<=M;l=r+1){
r=min(N/(tn=N/l),M/(tm=M/l));
ans+=R[tn][tm][r]-R[tn][tm][l-1];
}printf("%d\n",(ans%mod+mod)%mod);
}return 0;
}
另解
来自 @流风之回雪 大佬的赛时解法。
发现此题并没有强制在线,可以使用基于莫队的的做法。
静待大佬发题解……
(然而一年多过去了还是没有其他题解,那我就自己写写吧)
设一行的权值
莫队可以把问题转化成
到这里可以尝试
平均复杂度大概是