题目大意
有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; }