首先思考最暴力的方法: 我们用DFS直接搜索所有可能解, 那么对于每一层,有3种决策: 不选这个数, 选择这个数, 选择这个数的阶乘。 递归深度最大25, 时间复杂度O(3^25), 太大了, 要想办法降低时间复杂度。 还记得之前的简化版吗? 我们在四个集合中每个集合选择一个数字相加,问是否等于一个数S, 我们的方法是预处理两个集合中所有的情况,然后二分。 该题也可以采取相同的策略: 进行两次dfs, 每次递归深度n/2,这样就成功将复杂度降低到O((3^13)*log(3^13)) 。 这是理论上界, 事实上这里面有很多重复的,时间复杂度并没有那么高。
细节参见代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> #include<vector> #include<stack> #include<bitset> #include<cstdlib> #include<cmath> #include<set> #include<list> #include<deque> #include<map> #include<queue> #define max(a,b) a>b?a:b #define min(a,b) a<b?a:b using namespace std; typedef long long ll; const double PI = acos(-1.0); const double eps = 1e-6; const int INF = 1000000000; const int maxn = 35; const int max_cnt = 2000000+5; ll cal[maxn], a[maxn], n, k, S, ans; void init() { cal[0] = 1; for(int i = 1; i <= 20; i++) { cal[i] = cal[i-1] * i; } } struct node { ll sum, k; node(ll ss=0, ll kk=0):sum(ss), k(kk) {} bool operator < (const node& rhs) const { return sum < rhs.sum || (sum == rhs.sum && k < rhs.k); } }c[max_cnt],b[max_cnt]; map<node, int> p; map<node, int> :: iterator it; void dfs1(int d, ll sum, int _k) { if(_k > k) return ; //第一遍DFS, 求前n/2的组合情况 if(d > n/2) { p[node(sum, _k)]++; return ; } dfs1(d+1, sum, _k); if(sum+a[d] <= S) dfs1(d+1, sum+a[d], _k); if(a[d] <= 20 && sum+cal[a[d]] <= S) dfs1(d+1, sum+cal[a[d]], _k+1); } void dfs2(int d, ll sum, int _k) { if(_k > k) return ; //第二次DFS if(d > n) { it = p.lower_bound(node(S-sum,0)); //找到第一个大于等于答案的位置 for( ; it != p.end(); ++it) { //累加答案 if(it->first.sum == S-sum && _k+it->first.k <= k) ans += it->second; if(it->first.sum != S-sum) break; } return ; } dfs2(d+1, sum, _k); if(sum+a[d] <= S) dfs2(d+1, sum+a[d], _k); if(a[d] <= 20 && sum+cal[a[d]] <= S) dfs2(d+1, sum+cal[a[d]], _k+1); } int main() { init(); while(~scanf("%I64d%I64d%I64d",&n,&k,&S)) { for(int i=1;i<=n;i++) { scanf("%I64d",&a[i]); } p.clear(); ans = 0; dfs1(1,0,0); dfs2(n/2+1,0,0); printf("%I64d\n",ans); } return 0; }