### Description

  数据范围:(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));
}
原文地址:https://www.cnblogs.com/yoyoball/p/10179432.html