问题描述
众所周知,小包是一名非常喜欢吃甜点的小朋友,他在工作时特别爱吃下午茶里的甜食。
这天,下午茶小哥像往常一样送来了今天的 N 个甜点。小包对每个甜点有自己的喜爱值。但是今天的他不再贪心,并不想要喜爱值越高越好。他今天吃的甜点的喜爱值的和,一定要等于他的预期值 S。
但是他的预期值很高,小哥的甜点似乎不一定满足得了他,所以他准备了 M 个魔法棒,每个魔法棒可以对一个他要吃的甜点使用 1 次,使用后这个甜点的喜爱值会变成原来的喜爱值的阶乘。无法对一个甜点使用多次魔法棒,也不需要使用完所有魔法棒,也无法对不吃的甜点使用魔法棒。
小包很好奇,他有多少种方案,可以吃到喜爱值刚好为他的预期值的甜点。如果 2 种方案,食用了不同的甜点,或者对不同的甜点使用了魔法棒,都算作不同的方案。
数据范围
10%的数据保证 M = 0, 1 <= N <= 10
30%的数据保证 1 <= N <= 12
100%的数据保证 1 <= N <= 25, 0 <= M <= N, 1 <= S <=
这道题乍一眼看上去很像是动态规划的题目。但经过仔细的思考发现这条路是走不通的,状态压缩并不可行,而直接按背包那更是天方夜谭。
N很小但是又不是那么小,这完全不给状态压缩活路,但是也在暗示我们可以思考另外一种解法,那就是搜索。
单纯的搜索当然是不可行的,枚举一个甜点吃或不吃,用或者不用魔法棒,那么将是3种情况,那么理论上最大的时间复杂度是
这很显然已经超过了我们的限制。那么如何做呢。 OI界有不少搜索的优化方案。当我们在计数的时候,发现计数的原理满足乘法定律,那么就可以往折半搜索这里去靠近了。
n小但是不完全小,所以我们可以想办法让它更小。 我们不妨将这个n均等分成两部分,然后先分别在这两部分上搜索,看看能组合出一些什么样子的喜爱值。因为这相当于直接搜索了,所以能把每一种情况都模拟出来。
对于当前的喜爱值,我们只关心预期值-当前值是否能够被构造出来。 所以我们先搜索前n/2的部分,将所有的喜爱值组合用哈希表记录下来。
再搜索后面n/2的部分时,我们每搜索到一个结果,立刻在原有的哈希表中查询预期值-搜索结果是否存在,存在的话那么就相当于我们又多了一种方案。当然记得,魔法棒的使用次数不能超过m,所以我们还需要记录魔法棒使用了的次数,以防多算。
最后我们来思考一下剪枝,和进一步优化这个算法。 剪枝并没有太多出路。
观察数据范围,我们发现当喜爱值比较大的时候,它的阶乘必然超过最后的限制,所以可以直接不搜索阶乘那一步。
同样的,当加上这个喜爱值的阶乘以后超过了限制的话同样是不可行的,所以我们也应该把它舍去。
进一步优化这个算法,我们可以将折半写成折多几半,但是这样会增加编码的复杂度,况且对于这道题而言折半已经是足够优秀的算法了,因此没有必要折磨自己。
而且最后在统计的时候,多段仍然要一步步合并,难免出现些问题。
最后附上一段C++代码,ACM模式不是核心代码模式, 问就是我是OIer习惯了也懒得改而且写的还有些问题。 比如忘记考虑魔法棒使用次数小于m。忘了判断了。
不过这不重要。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <class T>
inline void read(T &x)
{
int fg = 1; char ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';ch = getchar()) fg = ch == '-' ? -1 : 1;
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + (ch ^ '0'); x *= fg;
}
#define ll long long
const int P = 70921;
const int N = 1e5 + 5;
int f(int x){ return (x % P + P) % P; }
struct hash_map
{
int last[N],cnt = 0,stk[N],tot = 0;
struct node{ int nxt,key,tot; } a[N];
void insert(int x)
{
int fg = 1;
for(int p = last[f(x)];p;p = a[p].nxt)
if(a[p].key == x) { ++a[p].tot, fg = 0; break; }
if(fg) a[++cnt] = (node){ last[f(x)],x,1 }, last[f(x)] = cnt, stk[++tot] = f(x);
}
ll get(int x)
{
for(int p = last[f(x)];p;p = a[p].nxt)
if(a[p].key == x) return 1ll * a[p].tot;
return 0ll;
}
} hash[2];
const int M = 30;
int n,m,Max;
ll S,a[M],s[M],ans = 0;
void preparation()
{
s[1] = 1, Max = 1;
for(int i = 2;i <= 19; ++ i)
s[i] = s[i - 1] * i, Max = s[i] <= S ? i : Max;
}
void dfs(int x,int lim,int fg,ll sum,int t)
{
if(sum > S || t > m) return;
if(x > lim)
{ hash[fg].insert(sum); return; }
dfs(x + 1,lim,fg,sum,t),
dfs(x + 1,lim,fg,sum + a[x],t);
if(a[x] <= Max) dfs(x + 1,lim,fg,sum + s[a[x]],t + 1);
return;
}
int main()
{
freopen("P23.in","r",stdin);
freopen("P23.out","w",stdout);
read(n), read(m), read(S);
for(int i = 1;i <= n; ++ i) read(a[i]);
preparation();
dfs(1,n / 2,0,0,0), dfs(n / 2 + 1,n,1,0,0);
for(int i = 1;i <= hash[0].tot; ++ i)
for(int p = hash[0].last[hash[0].stk[i]];p;p = hash[0].a[p].nxt)
if(hash[0].a[p].key <= S) ans += 1ll * hash[0].a[p].tot * hash[1].get(S - hash[0].a[p].key);
printf("%lld\n",ans);
fclose(stdin); fclose(stdout);
return 0;
}