题意
有n个数字,先在里面选择若个,然后可以把其中最多k个数字变成其的阶乘,问你有多少中方法可以使和变成S。
(1 ≤ n ≤ 25,0 ≤ k ≤ n,1 ≤ S ≤ 1016,1≤ai≤109)
思路
S非常大,S非常大,S非常大。重说三。所以要另想办法。有一个思路是如果我知道了一些数字可以凑成X,那么其可以凑成S的方案书便是剩下的那些数字凑成(S-X)的方案数。即我们可以把这些数字分成两部分,枚举第一部分在合法情况下得到的一种方案 Proi ,这钟方案的和是X,其中j个数字变成了阶乘。其方案数是在另一部分的数字中和凑成(S-X),并且变成阶乘的个数小于(k-j)的方案数。而第二个可以预处理出来。
用k个map记录把k个数字变成阶乘的方案数。
_m[i][s]表示把i个数字变成阶乘后凑成s的方案数。
然后对 i 处理前缀和。
然后枚举和统计就行了。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 25 + 5;
int n, k;
long long _;
long long a[N];
long long b[N], c[N];
int _b, _c;
int power(int n, int p) {
int res = 1;
for (int i=1; i<=p; i++) {
res *= n;
}
return res;
}
map<long long , long long> _m[N];
set<long long> _set;
long long _get(int n) {
long long res = 1;
for (int i=1; i<=n; i++) {
res *= i;
if (res > _) return _+1;
}
return res;
}
void solve(int s) {
int _s = s;
long long ans = 0;
int _cnt = 0;
for (int i=0; i<_b; i++) {
if (_s % 3 == 1) {
ans += b[i];
} else if (_s % 3 == 2) {
ans += _get(b[i]);
_cnt++;
}
_s /= 3;
if (ans > _) return ;
}
if (_cnt > k) return ;
_set.insert(ans);
_m[_cnt][ans]++;
return ;
}
int main () {
scanf ("%d%d%I64d", &n, &k, &_);
for (int i=0; i<n; i++) {
scanf ("%I64d", &a[i]);
}
for (int i=0; i<n; i++) {
if (i % 2 == 0) b[_b++] = a[i];
else c[_c++] = a[i];
}
for (int i=0; i<=25; i++) _m[i].clear();
for (int s = 0; s < power(3, _b); s++) {
solve(s);
}
set<long long >::iterator it;
for (it = _set.begin(); it != _set.end(); it++) {
// cout<< *it << ": " << _m[0][*it] << " ";
for (int j=1; j<=k; j++) {
_m[j][*it] += _m[j-1][*it];
// cout << _m[j][*it] << " ";
}// cout << endl;
}
long long D = 0;
// cout << _c << endl;
for(int s=0; s<power(3, _c); s++) {
int _s = s;
long long _sum = 0;
int _cnt = 0;
for (int i=0; i<_c; i++) {
if (_s % 3 == 1) {
_sum += c[i];
} else if (_s % 3 == 2) {
_sum += _get(c[i]);
_cnt++;
}
if (_sum > _) break;
_s /= 3;
}
if (_sum <= _ && _cnt <= k) {
// cout << k << " " << _ << " _ " << k-_cnt << " " << _-_sum << endl;
D += _m[k-_cnt][_-_sum];
}
}
printf ("%I64d\n", D);
return 0;
}