[SNOI2017]遗失的答案

题目

传送门

题解

第一个处理,我们可以将 \(N,L\) 同时 \(/G\),当然,如果 \(G\nmid L\),那么全部无解,输出 \(Q\)0 即可。

\(n=\frac{N}{G},l=\frac{L}{G}\),那么,这道题就被我们转化为

\([1,n]\) 之间,选一些数,其中必选 \(\frac{X}{G}\),使得他们的 \(\gcd\)\(1\)\(\text{lcm}\)\(n\)

注意,这里的 \(\gcd\)\(1\) 不等于所有数互质。

显然,我们选出的这些数一定都是由 \(\text{lcm}\) 的因数,考虑我们选择 \(\{x_1,x_2,x_3...x_k\}\) 这些数,怎么才能使得他们的 \(\gcd=1,\text{lcm}=l\)

由于 \(\gcd\)\(\text{lcm}\) 十分类似,我们只分析 \(\gcd\)

将所有 \(x\) 分解,设 \(x_i=p_{i,j}^{t_{i,j}}p_{i,j+1}^{t_{i,j+1}}...\)

如果 \(\exist x_i,t_{i,j}=0\) 对于所有 \(j\) 成立,那么他们的 \(\gcd=1\) ,也就是说,只要我们选的所有数中,有某一个数的这个质因数指数为 \(0\),而且所有的质因数都可以找到类似于这样的一个数,那么 \(\gcd(x_i)=1\)

类似地,\(\text{lcm}\) 也是同样的道理,只要我们选的所有数中,有某一个数的这个质因数指数和 \(l\) 相同,并且对于所有的质因数都可以找到这样的一个数,那么 \(\text{lcm}(x_i)=l\)

有了这个分析,我们进一步思考。

由于 \(L\le 10^8\),我们可以看一下,\(L\) 最多会有多少个因数?

\[2\times 3\times 5\times 7\times 11\times 13\times 17\times 19\times 23=223092870>10^8 \]

那么,\(L\) 最多只会有 \(9-1=8\) 个因数。

对于一个因数,我们可以维护他的每个质因数,因为只有八个因数,那么就会有 \(2^8\) ?并不是,由于我们每个因数都有 底下界( \(\gcd\) ),底上界( \(\text{lcm}\) ) 这两种情况,那么每个质因数我们维护两位二进制——\(00\) 表示上下界都不到达,\(01\) 表示底上界,\(10\) 表示底下界。

对于一个数 \(M\le 10^8\),我们有

它的因数个数最多不超过 \(800\) 个。

的结论,具体证明在这里

那么,我们来看一下怎么枚举因数的:

void dfs(const int i,int now,const int s){
    if(i==len){//枚举了一个因数
        ans[++cnt]=-1;//更新因数编号
        lst.push_back(mp(now,cnt));//并将其放进所有的因数集合中
        // printf("this num,now == %d, cnt == %d, s == ",now,cnt);print(s);Endl;
        f[cnt].resize(sz);
        // printf("modify_DWT(%d,%d,%d)\n",cnt,0,1);
        // printf("modify_DWT(%d,%d,%d)\n",cnt,s,1);
        modify_DWT(f[cnt],0,1);//要么就不选这个因数
        modify_DWT(f[cnt],s,1);//选上这个因数,那么其状态为 s 的那一位也为 1
    }else{
        int ti=ele[0][i],tl=ele[1][i];
        rep(t,0,tl){//枚举这个因数中,第 i 个质因数的次方
            if(t==0)dfs(i+1,now,s<<2|2);//如果他没有这个因数,那么他限定下界
            else if(t==tl)dfs(i+1,now,s<<2|1);//如果他的因数与 lcm 对应,那么他限定上界
            else dfs(i+1,now,s<<2);//十分平凡
            if(1ll*now*ti<=n)now*=ti;//保证因数在 n 的范围之内
            else return;
        }
    }
}

然后,我们设 \(f[i][j]\) 表示在前 \(i\) 个数中,选择的因数代码的状态 \(s\) 或起来等于 \(j\) 的方案数,那么就有

\[f[i][j]=\sum_{k\cup t=j}f[i-1][k]\times f[i-1][t] \]

俨然一副 \(FWT\) 的样子。

显然,最后我们需要的就是 \(1111...\) 的状态。

可以先将所有的 \(f[i]\)\(DWT\) 形式处理出来,然后对位累乘即可。

但是我们所求的是

\([1,n]\) 之间,选一些数,其中必选 \(\frac{X}{G}\),使得他们的 \(\gcd\)\(1\)\(\text{lcm}\)\(n\)

怎么 “必选 \(\frac{X}{G}\) ”?考虑容斥——将所有方案处理出来,再减去不选 \(\frac{X}{G}\) 的情况。

那么怎么求不选呢?累乘的时候不乘上 \(\frac{X}{G}\) 对应的 \(f[i]\) 即可。

为了快速处理,我们可以求出 \(f[i]\) 的前缀积和后缀积,这样可以 \(\mathcal O(N)\) 算出我们需要的数组。

最后询问我们得到的数组的原形式的最后一项即可(对应状态 \(1111....\)

但是,这道题有一个优化——因为我们的 \(DWT\) 过程都是 “单点改(初始化 \(f[i]\) 时我们只将 \(f[i][0],f[i][s]\) 赋值为 \(1\) ),单点问(最后我们只想问得到的数组的最后一项)”,那么,我们可以想一下怎么 \(\mathcal O(N)\) 地将我们的单点修改的影响直接加在 \(DWT\) 之后的数组上,怎么 \(\mathcal O(N)\) 地询问我们的最后一个位置的值?

这里不做过多解释(似乎不加这个优化吸了氧才堪堪过),具体对应的部分是代码中的 modify_DWT()getLast() 函数,配套 getLast() 的是 init[] 数组,可以自行钻研一下。

代码

#include<bits/stdc++.h>
using namespace std;

#define rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
#define erep(i,u) for(signed i=tail[u],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
typedef long long LL;
typedef pair<int,int> pii;
typedef unsigned long long ull;
typedef unsigned uint;
#define Endl putchar('\n')
// #define int long long
// #define int unsigned
// #define int unsigned long long

#ifdef _GLIBCXX_CSTDIO
#define cg (c=getchar())
template<class T>inline void qread(T& x){
    char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T>inline T qread(const T sample){
    T x=0;char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
#undef cg
template<class T>void fwrit(const T x){//just short,int and long long
    if(x<0)return (void)(putchar('-'),fwrit(-x));
    if(x>9)fwrit(x/10);
    putchar(x%10^48);
}
#endif
// template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
    inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
    return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}

const int MOD=1e9+7;
const int inv2=(MOD+1)>>1;
const int MAXSIT=800;

inline int Get(const int x){int n=1;while(n<=x)n<<=1;return n;}

inline void DWT_or(vector<int>&f,const int opt){
    //if opt==-1, DWT_or will be IDWT_or
    int N=Get((int)f.size()-1);
    f.resize(N);
    for(int t=2;t<=N;t<<=1)
        for(int i=0,p=t>>1;i<N;i+=t)for(int j=i;j<i+p;++j)
            f[j+p]=(0ll+f[j+p]+f[j]*opt+MOD)%MOD;
}
inline void DWT_and(vector<int>&f,const LL opt){
    int N=Get((int)f.size()-1);
    f.resize(N);
    for(int t=2;t<=N;t<<=1)
        for(int i=0,p=t>>1;i<N;i+=t)for(int j=i;j<i+p;++j)
            f[j]=(0ll+f[j]+f[j+p]*opt)%MOD;
}
inline void DWT_xor(vector<int>&f,const LL opt){
    int N=Get((int)f.size()-1);//pay attention to this -1
    f.resize(N);
    int x,y;
    for(int t=2;t<=N;t<<=1)
        for(int i=0,p=t>>1;i<N;i+=t)for(int j=i;j<i+p;++j){
            x=f[j],y=f[j+p];
            f[j]=(0ll+x+y)%MOD,f[j+p]=(0ll+x-y+MOD)%MOD;
            if(opt==-1)f[j]=1ll*f[j]*inv2%MOD,f[j+p]=1ll*f[j+p]*inv2%MOD;
        }
}

int ans[MAXSIT+5],cnt;

int n,g,l,mirl;
int init[1<<16|2];

inline void Init(){
    n=qread(1),g=qread(1),mirl=l=qread(1);
    if(l%g){
        rep(i,1,qread(1))writc(0,'\n');
        exit(0);
    }else l/=g,n/=g;
}

vector<int>ele[2];
int len,sz;//有多少个质因数,状态总数
//ele[0]:保存分解 l 的质因数
//ele[1]:保存分解 l 的这个因数的次方
vector<pii>lst;//保存每个因数以及其对应编号

inline int findPos(const int pos){
    vector<pii>::iterator it=lower_bound(lst.begin(),lst.end(),mp(pos,0));
    return it->second;
}

inline void Dec(){
    for(int i=2,t=0;i*i<=l;++i,t=0)if(l%i==0){
        while(l%i==0)l/=i,++t;
        ele[0].push_back(i);
        ele[1].push_back(t);
        // printf("this part, i == %d, t == %d\n",i,t);
    }
    if(l!=1)ele[0].push_back(l),ele[1].push_back(1);
    // printf("this part, i == %d, t == %d\n",l,1);
}

inline void MakeInit(){
    sz=1<<((len=ele[0].size())<<1);
    init[sz-1]=1;
    for(int i=1;i<sz;i<<=1)for(int j=sz-1;j>=sz-i;--j)
        init[j-i]=-init[j];
}

inline void modify_DWT(vector<int>&f,const int pos,const int delta){
    int n=Get(f.size()-1);
    f.resize(n);
    // printf("Now n == %d, pos == %d\n",n,pos);
    rep(i,0,n-1){
        if((i&pos)==0){
            // printf("udpate %d\n",i+pos);
            f[i+pos]+=delta;
            if(f[i+pos]>=MOD)f[i+pos]-=MOD;
        }
    }
}
inline int getLast(vector<int>&f){
    int ret=0,sz=f.size();
    rep(i,0,sz-1){
        ret+=f[i]*init[i];
        if(ret<0)ret+=MOD;
        else if(ret>=MOD)ret-=MOD;
    }return ret;
}

vector<int>f[MAXSIT+5];

inline int print(int now){
    while(now){
        if(now&1)putchar('1');
        else putchar('0');
        now>>=1;
    }
}

void dfs(const int i,int now,const int s){
    if(i==len){//枚举了一个因数
        ans[++cnt]=-1;//更新因数编号
        lst.push_back(mp(now,cnt));//并将其放进所有的因数集合中
        // printf("this num,now == %d, cnt == %d, s == ",now,cnt);print(s);Endl;
        f[cnt].resize(sz);
        // printf("modify_DWT(%d,%d,%d)\n",cnt,0,1);
        // printf("modify_DWT(%d,%d,%d)\n",cnt,s,1);
        modify_DWT(f[cnt],0,1);//要么就不选这个因数
        modify_DWT(f[cnt],s,1);//选上这个因数,那么其状态为 s 的那一位也为 1
    }else{
        int ti=ele[0][i],tl=ele[1][i];
        rep(t,0,tl){//枚举这个因数中,第 i 个质因数的次方
            if(t==0)dfs(i+1,now,s<<2|2);//如果他没有这个因数,那么他限定下界
            else if(t==tl)dfs(i+1,now,s<<2|1);//如果他的因数与 lcm 对应,那么他限定上界
            else dfs(i+1,now,s<<2);//十分平凡
            if(1ll*now*ti<=n)now*=ti;//保证因数在 n 的范围之内
            else return;
        }
    }
}

int sum[2][MAXSIT+5][1<<16];
//前缀积和后缀积
vector<int>t;

int all;

inline void preSolve(){
    rep(i,0,sz)sum[0][0][i]=sum[1][cnt+1][i]=1;
    rep(i,1,cnt)rep(j,0,sz-1)sum[0][i][j]=1ll*sum[0][i-1][j]*f[i][j]%MOD;
    fep(i,cnt,1)rep(j,0,sz-1)sum[1][i][j]=1ll*sum[1][i+1][j]*f[i][j]%MOD;
    t.resize(sz);
    rep(i,0,sz-1)t[i]=sum[0][cnt][i];
    all=getLast(t);
    // printf("all == %d\n",all);
}

signed main(){
    // ios::sync_with_stdio(false);
    Init();
    Dec();
    MakeInit();
    dfs(0,1,0);//枚举其所有满足条件的因数
    preSolve();
    sort(lst.begin(),lst.end());
    int pos;
    rep(i,1,qread(1)){
        pos=qread(1);
        if(pos%g || mirl%pos || pos/g>n){
            writc(0,'\n');continue;
        }
        pos=findPos(pos/g);
        // printf("pos == %d\n",pos);
        if(ans[pos]==-1){
            rep(i,0,sz-1)t[i]=1ll*sum[0][pos-1][i]*sum[1][pos+1][i]%MOD;
            // printf("getLast() == %d\n",getLast(t));
            ans[pos]=all-getLast(t);
            if(ans[pos]<0)ans[pos]+=MOD;
            else if(ans[pos]>=MOD)ans[pos]-=MOD;
        }writc(ans[pos],'\n');
    }
    return 0;
}
posted @ 2020-06-11 20:42  Arextre  阅读(192)  评论(0编辑  收藏  举报