题解 DZY Loves Math II
这是一个关于两个研究题解了好久都没看懂的沙雕的故事
其他部分上面两篇题解里有讲,这里略过。
令\(S\)的质因子有\(k\)个,分别为\(p_1, p_2 ... p_i\)
由题, \(n=\sum_{i=1}^{k}a_i\cdot p_i\) , 这里\(a_i\)代表\(p_i\)选了多少个
那么每种方案可以表示为一个\(k\)维向量 \(\vec{a}= (a_1, a_2, ... a_k)\),表示第\(i\)个物品被选择了\(a_i\)
”这个\(a_i\)可以表示成\(x * \frac{S}{p_i}+y\),也就是分为对\(\frac{S}{p_i}\)的倍数和余数来处理,\(x\)不同或者\(y\)不同意味着方案就是不同的。如果\(x\)不同,那么\(x\)每增大1,就会占用S的体积。“
这里不妨令每个\(p_i\)对\(n\)的贡献为\(val\), 则有\(val_i = x*(\frac{S}{p_i})*p_i+y*p_i=x*s+y*p_i\)
那么就有\(n=\sum_{i=1}^{k}val_i=S*\sum_{i=1}^{k}x_i+\sum_{i=1}^{k}y_i*p_i\)
对最后这个柿子式子的左右两边分别求方案数,然后乘起来即可
发现左边的式子等价于把\(t\)个物品分到\(k\)个盒子内(为什么是个\(t\)而不是\(\frac{n}{S}\)后面解释)
所以左边的方案数为\(C_{t*s+k-1}^{k-1}\), 右边需要跑背包, 为\(dp[n-t*s]\)
右边那坨式子看起来好像会小于\(S\), 然而其实并不是
如果每个\(y_i*p_i\)都很接近\(S\), 那么\(\sum_{i=1}^{k}y_i*p_i\)就可能超过\(S\)了
再说背包 多重->完全的部分
对于每个物品\(p_i\), 如果\(a_i*p_i>S\), 则\(dp[a_i*p_i]=dp[S+(a_i*p_i-S)]\)中, 显然有\(dp[a_i*p_i-S]\)个方案是不合法的, 减掉即可
然后根据\(\sum_{i=1}^{k}y_i*p_i<(k-1)*S\), 得到\(t\in[max(\frac{n}{S}-k+1, 0), \frac{n}{S}]\)打个遍历即可
细节上还要注意有的循环变量要开成\(long\ long\)或者, 着实很坑#define int long long
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 50
#define N2 15000010
#define ll long long
#define ull unsigned long long
//#define int long long
const ll p = 1e9+7;
ll s, q, n;
ll pri[N], cnt=0, sum=0;
ll dp[N2], inv[10];
bool init(ll s) {
ll n=s, f=1;
for (ll i=2; i*i<=n; ++i) {
if (n%i==0) {
pri[++cnt]=i; f*=i; sum+=i;
while (n%i==0) n/=i;
}
}
if (n>1) {pri[++cnt]=n; f*=n; sum+=n;}
if (f!=s) {while (q--) puts("0"); return true;}
ll end=cnt*s; // 这里不应该减一
dp[0] = 1;
for (ll i=1; i<=cnt; ++i) {
for (ll v=pri[i]; v<=end; ++v) {
dp[v] = (dp[v]+dp[v-pri[i]])%p;
//if (v>=s) dp[v] -= dp[v-s];
}
for (ll i=end; i>=s; --i) dp[i] = (dp[i]-dp[i-s]+p)%p;
}
inv[0]=inv[1]=1;
for (int i=2; i<8; ++i) inv[i] = (p-p/i)*inv[p%i]%p;
for (int i=2; i<8; ++i) inv[i] = (inv[i-1]*inv[i])%p;
return false;
}
ll qmul(ll a, ll b) {
ll ans=0; a%=p;
while (b) {
if (b&1) ans = (ans+a)%p;
a=(a<<1)%p; b>>=1;
}
return ans;
}
ll qpow(ll a, ll b) {
ll ans=1; a%=p;
while (b) {
if (b&1) ans = (ans*a)%p;
a=(a*a)%p; b>>=1;
}
return ans;
}
// 太奇幻了 这里利用k值很小这一特性,可以只计算约分后部分
ll C(ll n, ll k) { // 这玩意可以约分的!
//cout<<"C "<<n<<' '<<k<<endl;
if (n<k) return 0;
ll ans=1;
for (ll i=n-k+1; i<=n; ++i) //ans=(ans*i)%p;
ans = qmul(ans, i); //, cout<<"ans: "<<ans<<endl; // 这。。。
//cout<<"return "<<(ans*inv[k])%p<<endl;
return (ans*inv[k])%p;
}
void solve() {
scanf("%lld", &n);
if (n<sum) {puts("0"); return;}
if (n==sum) {puts("1"); return;}
n -= sum;
ll x=n/s, ans=0;
//cout<<x<<endl;
for (ll i=max(x-cnt+1, 0ll); i<=x; ++i) { // 这种题循环变量不要乱写int。。。
//cout<<"for "<<i<<' '<<i+cnt-1<<endl;
ans = (ans+ (C(i+cnt-1, cnt-1)*dp[n-i*s])%p )%p;
}
printf("%lld\n", ans);
}
signed main()
{
#ifdef DEBUG
freopen("1.in", "r", stdin);
#endif
cin>>s>>q;
if (init(s)) return 0;
while (q--) solve();
//cout<<dp[9]<<endl;
cout<<endl;
return 0;
}