题解 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;
}
posted @ 2021-08-03 15:06  Administrator-09  阅读(7)  评论(0编辑  收藏  举报