题意
有 n 个数字,先在里面选择若个,然后可以再在其中把最多 k 个数字变成其的阶乘,最后求这些数字的和。问你有多少种方法可以使和变成 S。
(1 ≤ n ≤ 25,0 ≤ k ≤ n,1 ≤ S ≤ 1016,1≤ai≤109)
链接:http://codeforces.com/problemset/problem/525/E
思路
这个题如果
S
比较小的话,自然有 dp 思路:
我们考查递推: f(i+1,j,s) 、 f(i+1,j,s+ai+1) 、 f(i+1,j+1,s+(ai+1)!) 分别加上 f(i,j,s) 。
问题就是
S
太大。这样是行不通的,不过我们发现第三维有大多数状态都是达不到的:因为数字太少,方案没有那么多。所以启发我们使用 std::map
的键代替第三维,枚举 std::map
中的键。
然,如果强行递推,不假思量的话是不会有好结果的。因为即使数字很少,最坏的状况下也有 325=847288609443≈8×1011 种不同的和。不管是空间还是时间都不允许你硬刚。
然,如果上述的和的数目开个根号的话那就没什么问题了。所以我们使用折半的技巧。处理出前面一半的数字再使用若干个阶乘的情况下产生某些和的方案数。同理处理后一半。枚举前面和后面合适的使用阶乘的数目,再枚举前面可以产生的和
X
,最后在另一半中查找是否有组成
代码:https://code.csdn.net/snippets/1583964/master/cf525E.cpp/raw