[Luogu4495] [BZOJ5302] [LOJ2523]
设 \(F[i][j]\) 表示选到 \(P\) 的第 \(i\) 个约数,选出的数的 \(gcd\) 值为 \(P\) 的第 \(j\) 个约数的方案数
则有 \(F[i][j]=F[i−1][j]+(1+∑_{gcd(a[k],a[i])==a[j]}F[i−1][k])∗(2^{s[i]−1})\)
其中 \(a[i]\) 表示 \(P\) 的第 \(i\) 个约数, \(s[i]\) 表示第 \(i\) 个约数的出现次数
分别对应继承上一轮 , 转移 , 只选这一种的情况
对于每一个询问 \(w_{i}\)我们容易得出答案就是 \(\sum{_{a[i]|w_{i}}F[n][i]}\)
事实上我们可以发现,如果一个数既是 \(P\) 的约数,又是 \(w_{i}\)的约数,那么它一定是 \(gcd(P,w_{i})\)约数,
因此我们只需要统计 \(\sum{_{a[i]|gcd(P,w_{i})}F[n][i]}\)
而这可以在DP之后就预处理出来 : \(G[i]=\sum{_{a[j]|a[i]}F[n][j]}\)
因此我们可以 \(O(1)\) 回答询问
这样搞好像是时间复杂度最优的 , 详见代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define Debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long LL;
const int INF=1e9+7;
inline LL read(){
register LL x=0,f=1;register char c=getchar();
while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();
return f*x;
}
const int MAXN=1e6+5;
const int MAXM=1e4+5;
const int mod=1e9+7;
int v[MAXN],sum[MAXN], num[MAXM],tot[MAXM],f[2][MAXM],g[MAXM];
int n,Q,P,cnt=0,now=0;
inline int add(int x,int y){x+=y;return x>mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}
inline int mul(LL x,int y){x*=y;return x>mod?x%mod:x;}
inline void init(){
sum[1]=2;
for(int i=2;i<=n;i++) sum[i]=mul(sum[i-1],2);
for(int i=1;i<=n;i++) sum[i]=dec(sum[i],1);
for(int i=1;i<=sqrt(P);i++) if(P%i==0) num[++cnt]=i;
for(int i=cnt;i>1;i--) if(P/num[i]!=num[i]) num[++cnt]=P/num[i];
for(int i=1;i<=n;i++){
int pos=lower_bound(num+1,num+cnt+1,v[i])-num;
tot[pos]++;//统计每个约数出现了几次,对应到后面选择的方案数
}
for(int i=1;i<=cnt;i++)if(tot[i]){
now^=1;
for(int j=1;j<=cnt;j++) f[now][j]=f[now^1][j];//不选:继承上一轮答案
for(int j=1;j<=i;j++)if(f[now^1][j]){//选的方案数
int pos=__gcd(num[i],num[j]);
pos=lower_bound(num+1,num+cnt+1,pos)-num;
f[now][pos]=add(f[now][pos],mul(f[now^1][j],sum[tot[i]]));//巧妙地组合起来转移
}
f[now][i]=add(f[now][i],sum[tot[i]]);//只选这一种
}
for(int i=1;i<=cnt;i++){
for(int j=1;j<=i;j++)
if(num[i]%num[j]==0) g[i]=add(g[i],f[now][j]);
}
}
int main(){
n=read(),Q=read(),P=read();
for(int i=1;i<=n;i++) v[i]=__gcd((int)read(),P);
init();
while(Q--){
int x=__gcd((int)read(),P);
int pos=lower_bound(num+1,num+cnt+1,x)-num;
assert(num[pos]==x);
printf("%d\n",g[pos]);
}
}