Codechef APRIL14 ANUCBC Cards, bags and coins 背包DP变形

  题目大意

    有n个数字,选出一个子集,有q个询问,求子集和模m等于0的方案数%1000000009。(n <= 100000,m <= 100,q <= 30)

  假设数据很小,我们完全可以做一个背包。

  我们沿着背包的思路,看能不能给物品分一下类,由于m比较小,完全按N个数字模M后的值进行分类,这样就变成了一个多重背包的问题。(转移时要乘上一个组合数)

  这时候的时间复杂度是n*m,还是不能过。

  对于DP时所枚举到的模m后余数j,它所进行的状态转移是一定的,如果把这些转移先预处理出来,时间复杂度就能得到有效减小。

  分析一下,转移方程是 F[i][j] = sigma(F[i][(j-i*k+m)%m]*C(count[i], k))%MOD,可以发现,(j-i*k+m)%m的值最多也只有m个,根本不需要枚举count[i]个这么多。

  设t = (i*k)%m,G[i][t] = sigma(C(count[i], k)) (k = 0..count[i] 且 i*k%m == t),G数组可以在O(n)的时间内预处理出来。

  新的转移方程就可以整理为 F[i][j] = sigma(F[i][(j-t+m)%m]*G[i][t])%MOD。 总的时间复杂度为O(n+m^3)。

  问题还没有结束,G数组的计算也需要一定的技巧。如果计算每个G[i][t]的值都算一次逆元和组合数,时间复杂度起码要加上一个log,会TLE。

  仔细分析可以发现,所计算的组合数C(count[i], k) (k = 0..count[i]),k是严格递增的,设temp = C(count[i], k-1),则C(count[i], k) = temp*(count[i]-(k-1))*inv[k]。

  inv数组可以利用逆元打表O(n)的方法来实现,具体可参考博文:http://blog.csdn.net/guhaiteng/article/details/52123385

  程序对拍过,但运行速度较慢。

  

#include <cstdio>
#include <cstring>

using namespace std;

typedef long long LL;
const int maxn = 100005;
const int MOD = 1000000009;
int n, m, Q;
int ccount[105];
LL g[105][105], inv[maxn], f[105][105];
int a[maxn], ans;

void add(LL &x, LL y)
{
    x += y;
    if (x >= MOD)
        x -= MOD;
}

void prepare()
{
    for (int i = 0; i < m; ++i)
        ccount[i] = 0;
    for (int i = 1; i <= n; ++i)
    {
        int temp = (a[i]%m+m)%m;
        ccount[temp] ++;
    }
    for (int i = 0; i < m; ++i)
        for (int j = 0; j < m; ++j)
            g[i][j] = 0;
    for (int i = 0; i < m; ++i)
    {
        LL temp = ccount[i];
        add(g[i][0], 1);
        add(g[i][i%m], ccount[i]);
        for (int j = 2; j <= ccount[i]; ++j)
        {
            (temp *= (ccount[i]-(j-1))) %= MOD;
            (temp *= inv[j]) %= MOD;
            add(g[i][(i*j)%m], temp);
        }
    }
}

void dp()
{
    memset(f, 0, sizeof(f));
    f[0][0] = g[0][0];
    for (int i = 1; i < m; ++i)
        for (int j = 0; j < m; ++j)
            for (int k = 0; k < m; ++k)
            {
                LL temp = f[i-1][(j-k+m)%m]*g[i][k]%MOD;
                add(f[i][j], temp);
            }
    ans = f[m-1][0];
}

int main()
{
    inv[1] = 1;
    for (int i = 2; i <= 100000; ++i)
        inv[i] = LL(MOD-MOD/i)*inv[MOD%i]%MOD;
    int Task;
    scanf("%d", &Task);
    while (Task --)
    {
        scanf("%d %d", &n, &Q);
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        while (Q --)
        {
            scanf("%d", &m);
            prepare();
            dp();
            printf("%d\n", ans);
        }
    }
    return 0;
}

 

posted @ 2017-02-10 21:37  Splay  阅读(358)  评论(0编辑  收藏  举报