题意:有一些人,每人拿一个号码,有两个门,门的值分别为A和B,要求把人分成两堆(可以为空)一堆人手持号码之和的数字根若等于A或者B就可以进入A门或者B门,要求两堆人分别进入不同的门,求有几种分配方式,如果A和B的值相等则算两种。
解法:dp。比赛的时候并不知道一个数%9+1就是数字根……煞费苦心的算了半天……只得出结论a+b的数字根等于a的数字根+b的数字根的数字根……没打表……于是T了……然后打了个表才过orz
设dp[i][j]表示用前i个人获得数字根为j的和的方案数,打一个表vector<int> tab[i][j]表示和j相加的数字根等于i的数。则可以得到方程dp[i][j] = dp[i - 1][j] + ∑dp[i - 1][tab[j][a[i]][k]],dp[i - 1][j]表示不选第i个数,后面的和表示选第i个数时可以转移来的所有状态之和。
答案即为dp[n][A],但是当A和B相等时,多出一种A选0个人的方案,需要特判。
代码:
因为一开始没想到上面那个特判……想了好多乱七八糟的特判= =代码好乱……【无视无视
#include <iostream> #include <cstdio> #include <cstring> #include <string> #include <cmath> #include <algorithm> #include <queue> #include <map> #include <set> #include <vector> #include <limits.h> using namespace std; typedef long long LL; const LL mod = 258280327; int t, n, A, B; int get_num(int x) { int tmp = x; while(tmp >= 10) { x = tmp; tmp = 0; while(x) { tmp += x % 10; x /= 10; } } return tmp; } vector <int> tab[10][10]; void init() { for(int i = 1; i <= 9; i++) for(int j = 1; j <= 9; j++) for(int k = 0; k <= 9; k++) { if(get_num(k + j) == i) tab[i][j].push_back(k); } } LL dp[100005][11]; LL a[100005]; int main() { init(); scanf("%d", &t); while(t--) { scanf("%d%d%d", &n, &A, &B); memset(dp, 0, sizeof dp); LL sum = 0; for(int i = 1; i <= n; i++) { scanf("%lld", &a[i]); sum += a[i]; } for(int i = 0; i <= n; i++) dp[i][0] = 1; LL ans = 0; int tmp = get_num(sum); if(tmp != get_num(A + B)) { if(tmp == A && tmp == B) { printf("2 "); continue; } else if(tmp == A) { printf("1 "); continue; } else if(tmp == B) { printf("1Zn"); continue; } else { printf("0 "); continue; } } if(sum == A) { if(sum == B) { printf("2 "); continue; } else { printf("1 "); continue; } } else if(sum == B) { printf("1 "); continue; } for(int i = 1; i <= n; i++) { for(int j = 1; j <= 9; j++) { dp[i][j] += dp[i - 1][j]; int len = tab[j][a[i]].size(); for(int k = 0; k < len; k++) { dp[i][j] += dp[i - 1][tab[j][a[i]][k]]; if(dp[i][j] > mod) dp[i][j] -= mod; } } } ans = dp[n][A]; if(get_num(sum) == B) ans++; printf("%lld ", ans); } return 0; }