数据范围:(1<=N<=10^{15}),保证(N)至多有(6)个质因子
Solution
数据范围明示要拿质因子搞事而且极有可能是状压qwq
那么首先分解一下(N)的质因数,然后我们就可以把它的因子按照所含的质因数分类了,含有质因数集合相同的因子归为一类,每一类都可以用二进制压成一个数,具体就是该位为(0)表示没有这个质因子,否则表示有,在将(N)质因数分解之后,我们就可以算出每一类分别有多少个因数了,若该类的状态为(st),记这个值为(val[st])
继续往状压的方向想,当前的选数情况是否也能压状态呢?答案是肯定的
根据题意,每个质因子最多出现两次,所以我们可以考虑用这样的一种方式状压:假设(N)分解出来有(m)个质因子,那么我们考虑将当前的选数状态压成一个(m+2)进制的数,每一位如果为(0),表示这个质因子没有出现过;如果为(m+1)表示这个质因子已经出现在了两个数里面;为(1sim m)表示这个质因子只出现在了一个数里面,并且这样的位置如果有两个的值相同,说明这两个质因子属于同一个数
稍微说明一下为什么需要给出现了(1)次的质因子标上不同的序号,举一个简单的例子,如果我选了(21),那么我还可以选一个(3)和一个(7),但是如果说我已经选了一个(3)和一个(7),那么我就不能选(21)了,而选了(21)的选了(3,7)的情况虽然说出现的质因子集合相同,但其实应该看做两种情况,标号就是为了避免这种问题
然后再说一下为什么只需要(1sim m)这(m)个数就可以解决标号的问题:因为每个质因子一旦被两个数包含,直接变成(m+1)这种表示”不可以再选“的状态了,所以我们只需要(m)个数就可以解决标号问题
再来看一下总的状态数:最坏情况下应该是一个(6)位的(8)进制数,那么总的状态数上限就是(2^{18}=262144),问题不大,直接记忆化搜索
最后是一些实现上的细节:关于那个标号的实现,是用到最小表示法,大概就是说:按照某个顺序扫一遍所有的位置,把我遇到的第一类标为(1),第二类标为(2),第三类标为(3)这样子
在这题里面就是:假设当前的状态转化过来的第(i)个质因子的标号为(pre[i]),当前新加进来的数所属的类的状态为(st),我们将加入后的新标号存在(now[i])中,那么我们首先将那些应该被标为(m+1)(也就是加入后出现了两次)的质因数在(now)中标为(m+1),剩下的出现在(st)中的位置全部标记为一个新的数字(能跟其他的类区别开来就ok),然后枚举所有的出现过的质因数,开始重标号的过程:如果说这个质因数已经被重标过号了直接跳过,否则将所有原来跟这个质因数标号一致的位置全部标成新的一类(如果那个位置已经在第一轮处理中被标成了(m+1)就跳过它)
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define ban (lis[0]+1)
using namespace std;
const int N=6e7+10,ST=(1<<6)+10,MOD=1e9+7;
int p[6000010],vis[N];
int pw[10];
ll rec[10],lis[10];
int f[1<<18],calced[1<<18];
int val[(1<<6)+10];
int cnt,ans,all,allst;
ll n;
int plu(int x,int y){return (1LL*x+y)%MOD;}
int mul(int x,int y){return 1LL*x*y%MOD;}
int St(int x){return 1<<x-1;}
int in(int st,int x){return st>>x-1&1;}
void prework(int n){
for (int i=2;i<=n;++i){
if (!vis[i])
p[++cnt]=i;
for (int j=1;j<=cnt&&i*p[j]<=n;++j){
vis[i*p[j]]=1;
if (i%p[j]==0) break;
}
}
}
void split(ll x){
lis[0]=0;
for (int i=1;1LL*p[i]*p[i]<=x&&x>1&&i<=cnt;++i){
if (x%p[i]) continue;
lis[++lis[0]]=p[i];
while (x%p[i]==0) x/=p[i],++pw[lis[0]];
}
if (x>1){
lis[++lis[0]]=x;
pw[lis[0]]=1;
}
}
void calc(){
for (int st=1;st<1<<lis[0];++st){
val[st]=1;
for (int i=1;i<=lis[0];++i)
if (in(st,i)) val[st]=mul(val[st],pw[i]);
}
}
void get_val(int st,int *rec){
for (int i=1;i<=lis[0];++i){
rec[i]=st%(lis[0]+2);
st/=lis[0]+2;
}
}
void get_st(int &st,int *rec){
st=0;
for (int i=lis[0];i>=1;--i) st=st*(lis[0]+2)+rec[i];
}
bool ok(int *now,int st){
bool vis[10];
memset(vis,0,sizeof(vis));
int cnt=0;
for (int i=1;i<=lis[0];++i){
if (!in(st,i)) continue;
if (now[i]==ban) return 0;
if (now[i]&&!vis[now[i]]){
++cnt;
vis[now[i]]=1;
}
}
return cnt<2;
}
void trans(int *pre,int st,int *now){
for (int i=1;i<=lis[0];++i) now[i]=pre[i];
for (int i=1;i<=lis[0];++i)
if (in(st,i)){
if (pre[i])
now[i]=ban;
else
now[i]=-1;//wait to mark
}
int id[10],cnt=0,tmp;
memset(id,0,sizeof(id));
for (int i=1;i<=lis[0];++i){
if (now[i]==ban||id[i]||!now[i]) continue;
++cnt; tmp=now[i];
for (int j=i;j<=lis[0];++j)
if (now[j]==tmp&&!id[j]){
id[j]=cnt; now[j]=cnt;//remark
}
}
}
int dfs(int nowst){
if (calced[nowst]) return f[nowst];
int now[10],nxt[10];
get_val(nowst,now);
int ret=1,tmp;
for (int st=1;st<(1<<lis[0]);++st){
if (!ok(now,st)) continue;
trans(now,st,nxt);
get_st(tmp,nxt);
ret=plu(ret,mul(val[st],dfs(tmp)));
}
calced[nowst]=1;
f[nowst]=ret;
return ret;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%lld",&n);
if (n==1){printf("0
"); return 0;}
prework(N-10);
split(n);
calc();
ans=dfs(0);
printf("%d
",plu(ans,MOD-1));
}