【题目描述】
小S决定送给小萌一个礼物。
这是一个特殊的礼物,它是一个集合,且集合所有的数的gcd为1,所有数的lcm为m。
小S他有多少种不同的送礼方案呢。两个送礼方案不同当且仅当存在某个x在一个方案中而不在另一个中。
答案对998244353取模。
【输入】
一行一个正整数m。
【输出】
输出答案对998244353取模。
【思路】
1.初步分析
首先,集合内的数一定是m的约数,于是第一个算法就是进行暴力搜索,判断集合合法性。由这个显然的结论我们可以知道,无论是什么算法,都必须对m进行质因数分解。但是m会达到
1
0
18
10^{18}
1018的规模,我们即使预处理素数也无法承受这样的数据规模,但是60分足以通过,我们姑且先不考虑这个问题。同时还有一些有趣的结论,m的不同质因数个数不超过15,m的约数个数不超过十万。
2.容斥
我们发现,如果把m表示为
m
=
∏
i
k
p
i
a
i
m=\prod_{i}^{k}p_{i}^{a_{i}}
m=∏ikpiai,其中p为质因数,那么满足合法集合必须满足:对于每一个i,集合必须存在一个数满足
p
i
p_{i}
pi项的次数为0,存在一个数满足
p
i
p_{i}
pi项次数恰为
a
i
a_{i}
ai,其余数的
p
i
p_{i}
pi项次数属于
[
0
,
a
i
]
[0,a_{i}]
[0,ai]。我们可以把上述限制提炼为2k个限制,直接计算不好计算,我们考虑容斥。注意到让集合中的数满足以上限制的基础上进行统计十分困难,但是如果我们钦定一些限制不被满足,计算方案就比较简单。所以我们考虑计算出至少有i个限制不被满足的方案,然后进行容斥:
a
n
s
=
∑
i
2
k
(
−
1
)
i
(
2
∏
j
k
R
j
−
l
j
+
1
−
1
)
ans=\sum_{i}^{2k}(-1)^{i}(2^{\prod_{j}^{k}R_{j}-l_{j}+1}-1)
ans=i∑2k(−1)i(2∏jkRj−lj+1−1)
其中
R
j
R_{j}
Rj表示集合中的数每一个质因子次数的上界,可以是
a
j
a_{j}
aj(满足限制)或
a
j
−
1
a_{j}-1
aj−1(不满足限制),
l
j
l_{j}
lj表示集合中的数每一个质因数的次数的下界,可以是0(满足限制)或1(不满足限制)。而集合的最大可能总数的计算可以通过乘法原理计算,即
∏
j
k
R
j
−
l
j
+
1
\prod_{j}^{k}R_{j}-l_{j}+1
∏jkRj−lj+1。而对于这其中的每个数,我们可以选或不选(满足"至少"的计算),除去空集的方案就是上述式子,配上容斥系数即可完成计算。也就是说,对于每个质因数,我们可以进行如下操作:
1.上下界限制均满足
2.上界满足
3.下界满足
4.上下界均不满足
我们可以通过搜索算法暴力枚举上述情况,复杂度为O( 4 2 k 4^{2k} 42k),复杂度有一点小爆炸。考虑优化,我们注意到 ,统计方案时我们只关心区间长度,而当前质因数次数区间为 [ 1 , a i ] [1,a_{i}] [1,ai]或 [ 0 , a i − 1 ] [0,a_{i}-1] [0,ai−1]对答案的贡献是完全相同的,所以我们可以把两者合并。复杂度优化为O( 3 2 k 3^{2k} 32k),可以通过。
3.m的分解
事实上,基于上述算法,我们发现,我们不再关心m的质因数具体是哪个数,而只关心m某个质因数的次数。这就有效降低了分解m的难度。考虑预处理出1e6以内所有质数,对m进行质因数分解。由于m只有1e18那么m最多有两个大于1e6的质因数,可能是以下三种情况:
1.
m
=
p
2
m=p^{2}
m=p2
2.
m
=
p
m=p
m=p
3.
m
=
p
q
m=pq
m=pq
对于第一种情况,我们可以把m开根判断。对于第二种情况,我们可以利用素数测试判断(代码中是用费马小定理进行枚(luan)举(gao)判断的)。如果不属于这两种情况,那么就属于第三种情况,那么问题就迎刃而解了。
代码:
#include<bits/stdc++.h>
#define re register
#define add(a,b) ((a)+(b)>=mod?(a)+(b)-mod:(a)+(b))
#define dec(a,b) ((a)>=(b)?(a)-(b):((a)-(b)+mod))
#define mul(a,b) ((long long)(a)*(b)%mod)
using namespace std;
const int N=1e6+5;
const int mod=998244353;
int n,b,c;
long long m;
inline int red()
{
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
int pcnt=0,p[N],cnt[N],tot=0;
bool vis[N];
inline int ksm(int a,int b)
{
int ret=1;
while(b)
{
if(b&1)ret=mul(ret,a);
b>>=1;
a=mul(a,a);
}
return ret;
}
long long qpow(long long a,long long b,long long mod)
{
long long ret=1;
while(b)
{
if(b&1)ret=mul(ret,a);
a=mul(a,a);
b>>=1;
}
return ret;
}
void pre()
{
for(int re i=2;i<=1e6;i++)
{
if(!vis[i])p[++pcnt]=i;
for(int re j=1,x=p[j]*i;j<=pcnt&&x<=1e4;j++,x=p[j]*i)
{
vis[x]=1;
if(i%p[j]==0)break;
}
}
}
inline bool lsr(long long m)
{
for(int re i=1;i<=100;i++)
if(qpow(p[i],m-1,m)!=1)return 0;
return 1;
}
void divide(long long m)
{
for(int re i=1;i<=pcnt;i++)
if(m%p[i]==0)
{
++tot;
while(m%p[i]==0)m/=p[i],cnt[tot]++;
}
if(m!=1)
{
if(lsr(m))cnt[++tot]=1;
else if(pow((long long)sqrt(m),2)==m)cnt[++tot]=2;
else cnt[++tot]=1,cnt[++tot]=1;
}
}
long long ans=0;
void dfs(int pos,int s,int now)
{
if(pos==tot+1)
{
ans+=mul(s,ksm(2,now)-1);
ans=(ans%mod+mod)%mod;
return;
}
dfs(pos+1,s,now*(cnt[pos]+1));
dfs(pos+1,-s*2,now*cnt[pos]);
if(cnt[pos]!=1)dfs(pos+1,s,now*(cnt[pos]-1));
}
int main()
{
pre();
scanf("%lld",&m);
divide(m);
dfs(1,1,1);
cout<<ans;
}