题意:输入n, m, k, 将n表示成多个数字相加的形式,但是但是不能包含对k取余余数为m的数字。
这个题正解是dp,但是数据量很小,只要姿势对,还是能爆过的,而且时间并不慢,9ms就跑完了,一开始用了记忆化搜索,但是去掉以后时间竟然是一样的。
这题其实一开始看了很久没看懂题意,看懂先随便暴了一下,样例出的就有点慢,然后换了种姿势重新暴,发现运行很快。
n最大30,如果枚举每个数字(也就是我一开始用的方法),会非常慢,有的数据10s都跑不完,但是我们可以换一种枚举方法,枚举每个数字出现了多少次,最后根据排列组合计算能排列成多少种答案,为了避免重复,我们保证先枚举到得数字总是比后枚举到得数字大。
对于有重复数字的排列,用总体排列除以相同数字的内部排列(如果我描述的不清楚可以直接看代码或者问度娘),但是总体数字可能会超long long,所以对整体排列我直接除以了最小数字的内部排列,这样就可以避免超long long,因为除去最小数字以后数字个数就可以控制在15以内了。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; typedef long long ll; int dp[40][40][40]; int pw[100]; int ans, m, k; int d[100]; ll AAA(int u, int b) { ll ans = 1; for(int i = b + 1; i <= u; i++) ans *= i; return ans; } void dfs(int n, int mx, int u, int s) { if(!n) { ll t = AAA(s, d[u - 1]); for(int i = 0; i < u - 1; i++) t /= AAA(d[i], 0); ans += t; return; } for(int i = min(mx, n); i > 1; i--) { if(i % k == m) continue; for(int j = 1; j * i <= n; j++) { d[u] = j; dfs(n - i * j, i - 1, u + 1, s + j); } } if(1 % k == m) return; d[u] = n; dfs(0, 0, u + 1, s + n); } void init() { pw[0] = 1; for(int i = 1; i <= 30; i ++) pw[i] = pw[i - 1] * 2; memset(dp, -1, sizeof(dp)); } int main() { int t, n, ks; init(); scanf("%d", &t); while(t--) { scanf("%d%d%d%d", &ks, &n, &m, &k); ans = 0; dfs(n, n, 0, 0); // dp[n][m][k] = ans; printf("%d %d\n", ks, ans); } return 0; }
