考虑到s的长度特别小,只有10,可以考虑状压dp。
设F[S][d]表示当选了集合S(用二进制压位表示)中的所有位置,对D取模的结果为d的方案总数;不难想到转移和初始化。
初始化:F[0][0]=1 0在这里表示空集合
转移:F[S][(d * 10 + s[i]-‘0‘) % D]=sum{F[S0][d]} S0是S的一个子集,并且刚好只比S少一个元素i
注意,重复的数字被算了多遍。样例当中就有。因此最后的答案要除以所有重复的数字个数的阶乘。
看代码就明白啦~
(再补充一个要用到状压技巧的题目特别是状压dp题目中常用的结论:当S‘是S的一个真子集时,必定有S‘<S。这样就可以在dp的时候确定枚举的顺序了。)
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const size_t Max_Len(20); const size_t Max_D(1050); const size_t Max_Bit(1500); unsigned int T; unsigned int D; unsigned int Len; char Str[Max_Len]; unsigned int F[Max_Bit][Max_D]; unsigned int Cnt[10]; int main() { cin >> T; while (T--) { memset(F, 0, sizeof(F)); memset(Cnt, 0, sizeof(Cnt)); cin >> Str >> D; Len = strlen(Str); for (unsigned int i = 0;i != Len;++i) ++Cnt[Str[i] - ‘0‘]; F[0][0] = 1; for (unsigned int S = 1;S < (1 << Len);++S) for (unsigned int i = 0;i < Len;++i) if (S & (1 << i)) for (unsigned int d = 0;d < D;++d) F[S][(d * 10 + Str[i] - ‘0‘) % D] += F[S ^ (1 << i)][d]; unsigned int &Ans = F[(1 << Len) - 1][0]; for (unsigned int i = 0;i != 10;++i) for (unsigned int g = 2;g <= Cnt[i];++g) Ans /= g; cout << Ans << endl; } return 0; }
BZOJ 1072
时间: 2024-12-09 22:24:23