题目
- 【题目大意】:给你 个数和 根魔法棒,魔法棒可以把数字变成它的阶乘,比如 。然后给你一个S,问:用这 个数,顶多有 个数可以变成阶乘,最后有几种方案可以凑出S这个数 ( ) 数值范围( )
思路
枚举(错误):
用dfs遍历枚举每种情况,每个数有3种状态,那总共是 ,虽然知道会T,但是我还是抱着侥幸的心理写了一发,差不多没有剪枝的T在4,然后我加了个目标函数的剪枝,T在17,恩……没戏了
动态规划:
- 【状态表示】: 表示 到 时,有 个数变成了阶乘,此时总和为 的方案数
- 【状态转移方程】:
- 【问题以及解决方案】:
问题1:
的范围:
,用数组不能存储
解决方法:
将
作为一个map来存储
map <llong, llong> dp[maxn][maxn];
问题2:
的初始化:
;没有数选择,总和为0,为1种方案,这里思考一个问题,如果是上面那个状态转移问题,会出现什么问题?就是你枚举到dp(i, j, k)的时候,
这三个状态可能并没有求出来
解决方案:
从小到大dp,由小状态去推大状态。
dp[i+1][j][key+a[i+1]] += dp[i][j][key]; //选了a[i]
dp[i+1][j][key] += dp[i][j][key]; //没有选a[i]
dp[i+1][j+1][key+f[a[i+1]]] += dp[i][j][key]; //选了a[i]的阶乘
问题3:
的状态还是有点多,最多最后一维可能还是有
解决方案:
, 左边n可以
一半,右边
一半。最后合并。
问题4:
如何合并?
解决方案:
的
是固定的,枚举
和
,
,
是固定的,最后枚举一个
表示总共有多少个变成了阶乘。所以
.
问题5:
如何遍历map的键值?
解决方案:
百度。
map<llong, llong>::iterator it;
llong key;
for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
{
key = it->first;
...
dp[i+1][j][key+a[i+1]] += dp[i][j][key];
...
}
算法流程
- 预处理阶乘。
- 输入数值,dp的初始化
- 分一半左右各自dp
- 合并dp
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#include<algorithm>
using namespace std;
typedef long long llong;
const llong INF = 1e16;
const int maxn = 25 + 5;
llong a[maxn];
llong f[maxn];
map<llong, llong>dp[maxn][maxn];
void Pre_frac()
{
f[1] = 1;
for (int i=2; i<20; ++i)
{
f[i] = f[i-1] * i;
//printf("f[%d]=%lld\n", i, f[i]);
}
return;
}
void debug(int n)
{
llong key;
map<llong, llong>::iterator it;
for (int i=1; i<=n; ++i)
for (int j=0; j<=i; ++j)
for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
{
key = it->first;
printf("dp[%d][%d][%lld]=%lld\n", i, j, key, dp[i][j][key]);
}
}
int main()
{
Pre_frac();
int n, m;
llong s;
while (scanf("%d %d %lld", &n, &m, &s) != EOF)
{
for (int i=1; i<=n; ++i)
scanf("%lld", &a[i]);
int half = n >> 1;
for (int i=0; i<maxn; ++i)
for (int j=0; j<maxn; ++j)
dp[i][j].clear();
map<llong, llong>::iterator it;
llong key;
dp[0][0][0] = 1;
for (int i=0; i<half; ++i)
for (int j=0; j<=i; ++j)
for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
{
key = it->first;
if (key>s)
continue;
dp[i+1][j][key+a[i+1]] += dp[i][j][key];
dp[i+1][j][key] += dp[i][j][key];
if (a[i+1]<19 && (key + f[a[i+1]] <= INF))
dp[i+1][j+1][key+f[a[i+1]]] += dp[i][j][key];
}
dp[n+1][0][0] = 1;
for (int i=n+1; i>half+1; --i)
for (int j=0; j<=(n-half); ++j)
for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
{
key = it->first;
if (key > s)
continue;
dp[i-1][j][key+a[i-1]] += dp[i][j][key];
dp[i-1][j][key] += dp[i][j][key];
if (a[i-1]<19 && (key + f[a[i-1]] <= INF))
dp[i-1][j+1][key+f[a[i-1]]] += dp[i][j][key];
}
//debug(n);
llong ans = 0;
for (int j=0; j<=half; ++j)
for (it=dp[half][j].begin(); it!=dp[half][j].end(); ++it)
for (int k=0; k<=m; ++k)
{
key = it->first;
if (j <= k)
ans += dp[half][j][key] * dp[half+1][k-j][s-key];
}
printf("%lld\n", ans);
}
return 0;
}