[HAOI2018]奇怪的背包

题目链接

题目大意

有一个模P意义下的背包,n个物品,每种有无限个,q个询问,问重量为w的方案。
题解:
首先,先考虑如何判断一些物品能否组成重w的背包。
根据贝祖定理,只要这些数和P的最大公约数是w的约数,就可以。

所以,对于本题,就是判断\(2^n\)中方案中,有多少种方案使得选择的数和P的最大公约数是w的约数。
显然,有一个\(O((n+q)*约数个数)\)的dp,会超时。

考虑优化:
对于询问,枚举w的约数x,根据之前的分析,只有x是P的约数才有解。
所以,枚举\(gcd(w,P)\)的约数即可。
由于P的约数不多,所以可以枚举P的约数y,预处理\(gcd(w,P)=y\)时的答案。
对于DP个过程,也可以用类似的方法:
由于要和P取gcd,所以每个V与\(gcd(V,P)\)等价,而不同的\(gcd(V,P)\)只有\(O(P的约数个数)\)个。
这样,枚举\(gcd(V,P)\)进行DP,就行了。
由于要用map定位约数,时间复杂度为\(O(M^2logM+(n+q)logP)\),能过。(M为P的约数个数)

代码

#include <stdio.h> 
#include <map> 
using namespace std;
#define md 1000000007 
map < int,int > mp;
int gcd(int a, int b) {
	while (b != 0) {
		int t = a % b;
		a = b;
		b = t;
	}
	return a;
}
int ys[1500],m = 0,dp[1500][1500],sl[1500],mi[1000010],ans[1500];
int main() {
	int n,q,P;
	scanf("%d%d%d", &n, &q, &P);
	for (int i = 1; 1ll * i * i <= P; i++) {
		if (P % i == 0) {
			ys[m++] = i;
			if (P / i != i) ys[m++] = P / i;
		}
	}
	for (int i = 0; i < m; i++) mp[ys[i]] = i;
	for (int i = 0; i < n; i++) {
		int a;
		scanf("%d", &a);
		sl[mp[gcd(P, a)]] += 1;
	}
	mi[0] = 1;
	for (int i = 1; i <= n; i++) mi[i] = (mi[i - 1] + mi[i - 1]) % md;
	dp[0][1] = 1;
	for (int i = 0; i < m; i++) {
		for (int j = 0; j < m; j++) {
			dp[i + 1][j] = (dp[i + 1][j] + dp[i][j]) % md;
			int t = mp[gcd(ys[j], ys[i])];
			dp[i + 1][t] = (dp[i + 1][t] + 1ll * (mi[sl[i]] - 1 + md) * dp[i][j]) % md;
		}
	}
	for (int i = 0; i < m; i++) {
		for (int j = 0; j < m; j++) {
			if (ys[i] % ys[j] == 0) ans[i] = (ans[i] + dp[m][j]) % md;
		}
	}
	for (int i = 0; i < q; i++) {
		int a;
		scanf("%d", &a);
		printf("%d\n", ans[mp[gcd(a, P)]]);
	}
	return 0;
}
posted @ 2019-08-14 21:01  lnzwz  阅读(149)  评论(0编辑  收藏  举报