[SNOI2017]遗失的答案-二刷

壹、题目

传送门

贰、思考

如果 \(G\nmid L\) 或者 \(G\nmid X\),那么无解,否则我们可以将 \(L,N\) 都除以 \(G\),现在我们的 \(L,N\) 都是在除以 \(G\) 之后的数值了.

再来看看我们现在的问题是什么:

\([1,N]\) 用选择一个集合 \(\{x_1,x_2,x_3,x_4,...\}\),必选 \(X\over G\),使得 \(\gcd(\{x_1,x_2,x_3,x_4,...\})=1,\text{lcm}(\{x_1,x_2,x_3,x_4,...\})=L\).

先将问题简化,假如我们现在不要求必选 \(X\over G\),那么该怎么做?

可以先将 \(L\) 进行分解,假设有 \(L=p_1^{k_1}p_2^{k_2}p_3^{k_3}...p_n^{k_n}\).

我们先分析一下 \(\gcd\),如果 \(\forall i,\exists j\;\text{s.t.}\; p_i\nmid x_j\),那么一定有 \(\gcd=1\).

同样,对于所有 \(p_i\),如果都有某个数 \(x_j\)\(p_i\) 这个因数的次数为 \(k_i\),且没有超过 \(k_i\) 的,那么也有 \(\text{lcm}=L\).

我们来考虑一下,对于每个质数,可能不触上下界、触及上界、下界,以及在选择多个数之后上下界同时触及共四种情况,又由于在 \(10^8\) 内,最多只有可能是 \(8\) 个不同的质数的乘积,我们考虑对于每个质数使用两位二进制表示,\(00\) 上下界均未触及,\(01\) 触及下界,\(10\) 触及上界,\(11\) 同时触及上下界,要存下 \(8\) 个质数就需要 \(2^{16}\) 的空间,可以接受,同时,两种情况的合成其实就是二进制下将他们按位或起来.

现在考虑我们选择的 \(\{x_1,x_2...\}\) 的这些 \(x_i\) 都是些什么?他们一定是 \(L\) 的因数,对于因数个数,有以下结论:

对于 \(n\le 1260\)\(d(n)\le \sqrt{3n}\)

对于 \(n> 1260\)\(d(n)\le \sqrt n\).

对于 \(L\),其因数个数最多不超过 \(800\) 个,我们可以考虑将其所有因数枚举出来,在这些因数中选择我们需要的因数,对于答案的统计,记 \(s_{i,j}\) 为选择了前 \(i\) 个因数,质数的状态为 \(j\) 时的方案数,同时记 \(f_{i,j}\)考虑\(i\) 个质数(选择或者不选),质数的状态为 \(j\) 时的方案数,那么有

\[s_{i,j}=\sum_{x|y=j}s_{i-1,x}f_{i,y} \]

或卷积,使用 \(\tt FWT\) 解决这个问题,最后的答案就是 \(s_{\text{factor number},1111...}\).

现在还有一个问题——如何强制选择 \(X\over G\)?考虑我们计算 \(s\) 的过程,如果不选择 \(X\over G\),只需要不乘上对应的 \(f\) 就可以了.

时间复杂度 \(\mathcal O(800\times 2^{16}\times 16)\).

叁、代码

using namespace Elaina;

const int mod=1e9+7;
const int maxn=8;
const int maxf=800;

inline void fwt_or(vector<int>& f,const int n,const int opt=1){
    for(int p=2;p<=n;p<<=1){
        int len=p>>1;
        for(int k=0;k<n;k+=p){
            for(int i=k;i<k+len;++i)
                f[i+len]=(f[i+len]+opt*f[i])%mod;
        }
    }
}

int N,G,L,Q,ori_L;
/** @brief the number of the prime*/
int n;

inline void input(){
    N=readin(1),G=readin(1),ori_L=L=readin(1),Q=readin(1);
}

/** @brief the result of decomposing L*/
vector<int>prime;
/** @brief correspond the prime, means the power of each prime*/
vector<int>power;
/** @brief decompose L into primes*/
inline void decompose(){
    for(int i=2;i*i<=L;++i)if(L%i==0){
        int t=0;
        while(L%i==0)L/=i,++t;
        prime.push_back(i),power.push_back(t);
    }
    if(L>1){
        prime.push_back(L),power.push_back(1);
    }
    n=prime.size();
}

/** @brief the f array of each factor*/
vector<int>f[maxf+5];
/** @brief record the factor, include it's id*/
vector<pii>factor;
int fcnt;
/**
 * @brief find the factor of L
 * @param i the index of prime
 * @param now the current factor that we get
 * @param s the current situation-chain
*/
void dfs(const int i,int now,const int s){
    // if this is the lastest prime, which means we've got a factor @p now
    if(i==n){
        // printf("now == %d, s == %d\n",now,s);
        f[++fcnt].resize(1<<(n<<1));
        // printf("f[%d].resize(%d)\n",fcnt,1<<(n<<1));
        
        // -------------- you can't use f[fcnt][0]=f[fcnt][s]=0, because there is a situation where s = 0 ----------------------
        ++f[fcnt][0];++f[fcnt][s];
        factor.push_back(mp(now,fcnt));
        // two situations : choose or not
    }else{
        // the maximum power that the factor could hold
        int up=power[i];
        for(int p=0;p<=up;++p){
            // touch the lowest
            if(p==0)dfs(i+1,now,s<<2|1);
            // touch the highest
            else if(p==up)dfs(i+1,now,s<<2|2);
            // very simple
            else dfs(i+1,now,s<<2);
            // with power of prime[i] increasing, now should also multiply prime[i]
            
            // pay attention to the range!!!
            if(1ll*now*prime[i]>N)break;
            now*=prime[i];
        }
    }
}

inline int Find(const int fac){
    int pos=lower_bound(factor.begin(),factor.end(),mp(fac,0))->se;
    return pos;
}

int pre[805][1<<16],suf[805][1<<16];
inline void getS(){
    rep(i,1,fcnt)fwt_or(f[i],1<<(n<<1));
    // pre[0].resize(1<<(n<<1));
    // pre[0][0]=1;fwt_or(pre[0],1<<(n<<1));
    rep(j,0,(1<<(n<<1)))pre[0][j]=suf[fcnt+1][j]=1;
    for(int i=1;i<=fcnt;++i){
        for(int j=0;j<(1<<(n<<1));++j)
            pre[i][j]=1ll*pre[i-1][j]*f[i][j]%mod;
    }
    for(int i=fcnt;i>=1;--i){
        for(int j=0;j<(1<<(n<<1));++j)
            suf[i][j]=1ll*suf[i+1][j]*f[i][j]%mod;
    }
}

/** @brief memorize the answer of each factor*/
int ans[805];

vector<int>arr;

signed main(){
    input();
    if(L%G){
        while(Q--)writc(0,'\n');
        return 0;
    }L/=G,N/=G;
    decompose(); dfs(0,1,0); getS();
    sort(factor.begin(),factor.end());
    for(int i=0;i<(1<<(n<<1));++i)arr.push_back(pre[fcnt][i]);
    fwt_or(arr,1<<(n<<1),-1);
    int all=arr[(1<<(n<<1))-1];
    rep(i,1,fcnt)ans[i]=-1;
    while(Q--){
        int x=readin(1);
        if(x%G || ori_L%x){
            writc(0,'\n');continue;
        }x/=G;
        int pos=Find(x);
        if(ans[pos]!=-1){
            writc(ans[pos],'\n');continue;
        }
        for(int j=0;j<(1<<(n<<1));++j)
            arr[j]=1ll*pre[pos-1][j]*suf[pos+1][j]%mod;
        fwt_or(arr,1<<(n<<1),-1);
        ans[pos]=((all-arr[(1<<(n<<1))-1])%mod+mod)%mod;
        writc(ans[pos],'\n');
    }
    return 0;
}

肆、用到の小 \(\tt trick\)

在这种情况下,可以考虑将上界、范围同时除以下界,以简化我们要求的问题.

其次,\(\gcd\) 限定下界,\(\rm lcm\) 限定上界,这种上下界问题可以使用类似数位 \(DP\) 并结合状压 \(DP\) 进行解决,这里用二位二进制就有刚好 \(2^2\) 表示四种状态.

然后,遇到类似 “不选择”、“必须选择” 等类型的问题,如果正着来太难,可以考虑正难则反,从总体答案减去 “选择”、“不选择” 的情况.

对于处理 求乘积、求累和 需要从中间去掉一项,可以考虑使用维护前缀和、后缀和 然后从中间挖掉要去掉的那一项.

posted @ 2021-02-03 21:52  Arextre  阅读(52)  评论(0编辑  收藏  举报