bzoj 5302 [Haoi2018]奇怪的背包 dp+数论

博客主要介绍了如何将奇怪的背包问题转化为求解满足特定模条件的数的选择,并利用扩展欧几里得算法证明了gcd约束。通过动态规划方法计算不同p的约数组合的gcd方案数,最终得出询问w的解答。时间复杂度为O(p + m^2 * logp + q),其中m大约在2 * 10^3,可应对该问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

题目传送门

解法

  • 可以将问题转化成这样一个问题:选出一些数 a 1 , a 2 , … , a m a_1,a_2,\dots,a_m a1,a2,,am,使得存在这样的 x 1 , x 2 , … , x m x_1,x_2,\dots,x_m x1,x2,,xm满足 ∑ i = 1 m a i x i ≡ w ( m o d p ) \sum_{i=1}^ma_ix_i\equiv w\pmod p i=1maixiw(modp)
  • 显然需要满足的条件就是 g c d ( a 1 , a 2 , … , a m , p ) ∣ w gcd(a_1,a_2,\dots,a_m,p)|w gcd(a1,a2,,am,p)w,这个可以用类似于扩展欧几里得的方法证明。
  • 那么,我们求出总共 m m m p p p的约数,分别为 b 1 , b 2 , … , b m b_1,b_2,\dots,b_m b1,b2,,bm。令 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个约数中选取若干个最后的 g c d gcd gcd为第 j j j个约数的方案数。
  • 转移比较简单, f [ i ] [ j ] = f [ i − 1 ] [ j ] + ( [ i = = j ] + ∑ k [ g c d ( b [ i ] , b [ k ] ) = = b [ j ] ] f [ i − 1 ] [ k ] ) × ( 2 ∣ s [ i ] ∣ − 1 ) f[i][j]=f[i-1][j]+([i==j]+\sum_k[gcd(b[i],b[k])==b[j]]f[i-1][k])\times(2^{|s[i]|}-1) f[i][j]=f[i1][j]+([i==j]+k[gcd(b[i],b[k])==b[j]]f[i1][k])×(2s[i]1)。具体实现的时候可以枚举 k k k,那么 j j j自然也得出来了。
  • 然后考虑最后的答案怎么处理,令 g [ i ] g[i] g[i]表示所有约数中选择若干个约数使得 g c d gcd gcd为第 i i i个约数的约数的方案数,那么显然有 g [ i ] = ∑ b [ j ] ∣ b [ i ] f [ n ] [ j ] g[i]=\sum_{b[j]|b[i]}f[n][j] g[i]=b[j]b[i]f[n][j]
  • 那么,现在对于某一个询问 w w w,答案显然为 g [ n u m [ g c d ( w , p ) ] ] g[num[gcd(w,p)]] g[num[gcd(w,p)]]
  • 时间复杂度: O ( p + m 2 log ⁡ p + q ) O(\sqrt p+m^2\log p+q) O(p +m2logp+q)
  • m m m大概在 2 × 1 0 3 2×10^3 2×103左右,可以通过此题。

代码

#include <bits/stdc++.h>
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 1000010, M = 2010, Mod = 1e9 + 7;
int a[N], b[N], g[N], pw[N], sum[N], f[M][M];
int gcd(int x, int y) {return y ? gcd(y, x % y) : x;}
void add(int &x, int y) {x += y; if (x >= Mod) x -= Mod;}
int main() {
	int n, q, p, m = 0; read(n), read(q), read(p);
	for (int i = 1; i <= n; i++) read(a[i]), a[i] = gcd(a[i], p);
	for (int i = 1; i * i <= p; i++)
		if (p % i == 0) {
			b[++m] = i;
			if (i * i != p) b[++m] = p / i;
		}
	sort(b + 1, b + m + 1); pw[0] = 1;
	for (int i = 1; i <= n; i++) pw[i] = 2ll * pw[i - 1] % Mod;
	for (int i = 1; i <= n; i++) {
		a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b;
		sum[a[i]]++, add(pw[i], Mod - 1);
	}
	pw[0] = 0;
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= m; j++) f[i][j] = f[i - 1][j];
		for (int j = 1; j <= m; j++) {
			int k = gcd(b[i], b[j]); k = lower_bound(b + 1, b + m + 1, k) - b;
			add(f[i][k], 1ll * f[i - 1][j] * pw[sum[i]] % Mod);
		}
		add(f[i][i], pw[sum[i]]);
	}
	for (int i = 1; i <= m; i++)
		for (int j = 1; j <= m; j++)
			if (b[i] % b[j] == 0) add(g[i], f[m][j]);
	while (q--) {
		int x; read(x);
		int y = gcd(x, p); y = lower_bound(b + 1, b + m + 1, y) - b;
		cout << g[y] << "\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值