P4495 [HAOI2018]奇怪的背包
题意
考虑\(n=1\)的情况,我们由裴蜀定理可知,\(k*a_1\%P\)能表示\(\gcd(a_1,P)\)的所有倍数。
扩展到多个数也是同理(不会证):
\((k_1a_1+k_2a_2+...+k_na_n)\%P\)能表示出\(\gcd(a_1,a_2,...,a_n)\)的所有倍数。
于是令\(v_i=\gcd(v_i,P)\)后问题是等价的。
之后处理出\(P\)的所有约数,假设数量为\(m\),存在\(a\)数组中。
设\(f_{i,j}\)表示考虑\(P\)的前\(i\)个约数,取出的数的\(\gcd\)为\(a_j\)的方案数,\(cnt_i\)表示\(a_i\)这个约数在\(v\)(取过\(\gcd\)的)中出现的次数。
\(f_{i,k}+=f_{i-1,j}*(2^{cnt_i}-1)\)
\(f_{i,i}+=2^{cnt_i}-1\)
之后对于每个\(w_i\),我们枚举它的约数:
\(ans=\sum\limits_{a_i|w}f_{n,i}\)。
但这样询问\(mq\)的,我们要进一步优化:
发现\(a_i\)也是\(P\)的约数,所以\(a_i|(\gcd(P,w))\),因此可以使\(w=\gcd(w,P)\)。
我们预处理\(g_i=\sum\limits_{a_j|a_i}f_{n,j}\),这样就可以\(O(1)\)处理每次询问了。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
const int maxm=5e4+10;
const int mod=1e9+7;
int n,m,P,Q;
int a[maxm],val[maxn],cnt[maxm],pw[maxn],g[maxm];
int f[2][maxm];
int main()
{
scanf("%d%d%d",&n,&Q,&P);
for(int i=1;i<=n;i++)scanf("%d",&val[i]),val[i]=__gcd(val[i],P);
for(int i=1;i*i<=P;i++)
{
if(P%i)continue;
a[++m]=i;
if(P!=i*i)a[++m]=P/i;
}
sort(a+1,a+m+1);
for(int i=1;i<=n;i++)cnt[lower_bound(a+1,a+m+1,val[i])-a]++;
pw[1]=2;
for(int i=2;i<=n;i++)pw[i]=1ll*pw[i-1]*2%mod;
for(int i=1;i<=n;i++)pw[i]=(pw[i]-1+mod)%mod;
for(int i=1;i<=m;i++)
{
int now=i&1;
for(int j=1;j<=m;j++)f[now][j]=f[now^1][j];
for(int j=1;j<i;j++)
{
int k=__gcd(a[i],a[j]);
k=lower_bound(a+1,a+m+1,k)-a;
f[now][k]=(f[now][k]+1ll*f[now^1][j]*pw[cnt[i]]%mod)%mod;
}
f[now][i]=(f[now][i]+pw[cnt[i]])%mod;
}
for(int i=1;i<=m;i++)
for(int j=1;j<=i;j++)
if(a[i]%a[j]==0)g[i]=(g[i]+f[m&1][j])%mod;
while(Q--)
{
int k;scanf("%d",&k);
k=__gcd(k,P);
printf("%d\n",g[lower_bound(a+1,a+m+1,k)-a]);
}
return 0;
}