题解 P6274 [eJOI2017]六

搬运一下自己的博客


神仙思路。

状压套状压,对于每一个 $N$ 的因数,我们可以求出它带有哪些 $N$ 的质因数(注意,这里不要求质因数次数,因为两个数只要含有同一个质因数就不互质了)。然后用乘法原理优化一下就可以 $O(k2^k)$ 求出含有这些质因数的数的个数。

然后状压,三进制,$2^k-1$ 位,第 $i$ 位的值代表有多少个质因数情况为 $i$ 的。

这样可能不好理解,举个例子:

$N=2^2 \times 3^2 \times 5 \times 7$

所以 $k=4$ ,然后比如像 $84=2^2 \times 3 \times 7$ 很显然是 $N$ 的因数,此时质因数情况为 $1101$

然后记忆化搜索即可。

但是仍然需要优化,首先把 $3$ 进制变成 $4$ 进制更加好写,但是此时需要用两个 $unsigned \ \ long \ \ long$ 存,此时用 $map$ 又多一个 $log$ (虽然没多大事貌似),考虑使用 $hash$ ,就两位,还不用担心取模耗时间( $ull$ ),然后貌似就可以了

细节看代码:

#include<cstdio>
#include<cctype>

#define ull unsigned long long

#define maxn 101
#define maxN 20000002
#define mod 1000000007

inline long long read(){
    long long r=0,f=0;
    char c;
    while(!isdigit(c=getchar()))f|=(c=='-');
    while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar();
    return f?-r:r;
}

int k,cs[maxn],cnt[maxn],rel[maxn][maxn];

long long n,m,p[maxn],f[maxN];//f尽量开大,hash冲突概率小一些

inline void add_cnt(long long x){
    int zt=0;
    for(int i=0;i<k;i++)
        zt|=(!(x%p[i])<<i);
    cnt[zt]++;
}

inline int val(ull x,ull y,int i){
    return (i<32?(x>>(i<<1)):(y>>((i-32)<<1)))&3;
}

inline void add(ull &x,ull &y,int i){
    i<32?x+=1ull<<(i<<1):y+=1ull<<((i-32)<<1);
}

long long solve(ull x,ull y){
    ull hx=x*131+y;//hash
    hx%=maxN;
    if(f[hx])return f[hx];
    long long ans=1;
    for(int i=1;i<(1<<k);i++){
        if(val(x,y,i)&2)continue;
        ull xc=x,yc=y;
        for(int j=1;j<=rel[i][0];j++)
            if(val(x,y,rel[i][j])^2)add(xc,yc,rel[i][j]);
            //注意,这里如果等于2并不代表无解,因为没有真正加一个数进来
            //所以在这一位上超过2没有问题,如果要在这一位真正加一个数会在上面判断
        (ans+=1ll*cnt[i]*solve(xc,yc)%mod)%=mod;
    }
    return f[hx]=ans;
}

int main(){
    n=m=read();
    for(long long i=2;i*i<=m;i++){//找质因数
        if(m%i)continue;
        while(!(m%i))m/=i,cs[k]++;
        p[k++]=i;
    }
    if(m>1)cs[k]=1,p[k++]=m;
    for(int i=1;i<(1<<k);i++){
        for(int j=1;j<(1<<k);j++)//看哪些数与这个数不互质
            if(i&j)rel[i][++rel[i][0]]=j;
        cnt[i]=1;
        for(int j=0;j<k;j++)//乘法原理
            if((i>>j)&1)cnt[i]*=cs[j];
    }
    printf("%lld",(solve(0,0)-1+mod)%mod);//记得减1,因为不能不放数字
    return 0;
}

 

posted @ 2021-12-16 10:43  一叶知秋‘  阅读(68)  评论(0编辑  收藏  举报