[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  
#include  
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;
}

你可能感兴趣的:([HAOI2018]奇怪的背包 题解)