P4495 [HAOI2018]奇怪的背包
题意
给你 \(n\) 个数,每次询问给出一个 \(w\),求有多少种选数的方式,使得每种数可以重复使用的情况下,使得加和在 \(p\) 的剩余系下为 \(w\)。
Solution
首先考虑判断一个数 \(v\) 是否可行,这样的话相当于要解决:
可以套路地转化成方程组来求解:
但是我们只需要判断有没有解,所以只要判断:\(\gcd(v,p)\) 是否是 \(w\) 的因子。
那如果有很多数 \(v_1,v_2,\cdots,v_k\) 呢?可以大胆猜测,充要条件就是:\(\gcd(v_1\sim v_k,p)\) 是 \(w\) 的因子。
于是问题转化成有多少种选取子集的方式,使得子集元素的 \(\gcd\) 和 \(p\) 的最大公约数是 \(w\) 的因子。
\(\color{red}{\bigstar}\) 然后直接考虑 \(dp_{i,j}\) 表示考虑完前 \(i\) 个物品后与 \(p\) 的 \(\gcd\) 为 \(j\) 的方案数。为了优化,我们可以把每个 \(a_i\) 和 \(p\) 先取一次 \(\gcd\),然后相同的压缩起来,这样总点数不超过 \(p\) 的因子个数即 \(p^{\frac{1}{3}}\)。那我们假设第 \(i\) 种数有 \(s_i\) 个,那么转移就是:
这样转移不是很方便,可以采用刷板法。
由于 \(j\) 的取值只有 \(p\) 的因子个,所以 dp 变成了 \(O(\sigma(p)^2)\)。
然后考虑求答案,那么应该是:
为了减小 \(w\) 的规模,我们对它也和 \(p\) 做一次 \(\gcd\)。所以变成了:
其实是一样的,只不过这样的话,我们就不需要每次询问都枚举,因为 \(\gcd(w,p)\) 的因子一定还是 \(p\) 的因子,所以我们只需要在这 \(\sigma(p)\) 个数之间预处理答案就可以了,还是 \(O(\sigma(p)^2)\)。
Code
// Problem: P4495 [HAOI2018]奇怪的背包
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4495
// Memory Limit: 500 MB
// Time Limit: 2000 ms
#include<bits/stdc++.h>
// #define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb emplace_back
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define all(a) a.begin(),a.end()
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
#define int long long
using namespace std;
const int MAXN=2e3+10;
const int MOD=1e9+7;
int ksm(int a,int p){
int ret=1;while(p){
if(p&1) ret=ret*a%MOD;
a=a*a%MOD; p>>=1;
}return ret;
}
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
map<int,int> g;
int lsh[MAXN*MAXN],a[MAXN],s[MAXN],tot,d[MAXN],cnt;
int dp[MAXN][MAXN],ans[MAXN];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,q,p;
cin>>n>>q>>p;
rep(i,1,n) cin>>lsh[i],lsh[i]=gcd(lsh[i],p);
sort(lsh+1,lsh+1+n);
rep(i,1,n) if(lsh[i]!=lsh[i-1]){a[++tot]=lsh[i],s[tot]=1;}else s[tot]++;
for(int i=1;i*i<=p;i++) if(p%i==0){
d[++cnt]=i;if(i*i!=p) d[++cnt]=p/i;
}
sort(d+1,d+1+cnt);
rep(i,1,cnt) g[d[i]]=i;
dp[0][cnt]=1;
rep(i,1,tot) rep(j,1,cnt){
int k=g[gcd(a[i],d[j])];
(dp[i][j]+=dp[i-1][j])%=MOD;
(dp[i][k]+=dp[i-1][j]*(ksm(2,s[i])+MOD-1)%MOD)%=MOD;
}
rep(i,1,cnt) rep(j,1,i) if(d[i]%d[j]==0) ans[i]=(ans[i]+dp[tot][j])%MOD;
while(q--){
int w;cin>>w;
cout<<ans[g[gcd(w,p)]]<<'\n';
}
return 0;
}