题目链接:http://codeforces.com/problemset/problem/525/E
题意:
给定n个数,k个感叹号,常数S
下面给出这n个数。
目标:
任意给其中一些数变成阶乘,至多变k个。
再任意取一些数,使得这些数和恰好为S
问有多少方法。
思路:
三进制状压,0代表不取,1代表取阶乘,2代表直接取;
中途查找,节约空间;
代码如下:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
ll n, k, s, tmp;
ll san[30], jc[1000], a[30];
map<ll, int>mp[2][30]; //2是归属范围 ,0代表前一半,1代表后一半
ll re[30],b[30], d[30], top;
ll cal(ll x)
{
if(x >= tmp) return -1;
else return jc[x];
}
void work(int x)
{
for(int i = 0; i < san[top]; i++)
{
int cnt = 0; //阶乘的个数
ll sum = 0;
int t = i, id = 0;
while(t)
{
if(t % 3 == 1)
{
cnt++; sum += d[id];
if (d[id] < 0)
{
sum = s + 1; break;
}
}
else if ((t % 3) == 2)
{
sum += b[id];
}
if (cnt > k || sum > s)break;
t /= 3; id++;
}
if (cnt <= k && sum <= s) mp[x][cnt][sum]++;
}
}
int main()
{
scanf("%I64d%I64d%I64d", &n, &k , &s);
san[0] = 1;
for(int i = 1; i < 30; i++)
{
san[i] = san[i-1] * 3;
}
jc[1] = 1;
for(int i = 2; ; i++)
{
jc[i] = jc[i-1] * i;
if(s / jc[i] <= i)
{
tmp = i+1;
break;
}
}
for(int i = 0; i < n ; i++)
{
scanf("%I64d", &a[i]);
re[i] = cal(a[i]);
}
for(int i = 0; i < n/2; i++)
{
b[i] = a[i];
d[i] = re[i];
}
top = n / 2;
work(0);
for (int i = n / 2; i < n; i++)
{
b[i - n / 2] = a[i];
d[i - n / 2] = re[i];
}
top = n - n / 2;
work(1);
ll ans = 0;
for(int i = 0; i <= k; i++)
{
for(map<ll,int>::iterator h = mp[0][i].begin(); h != mp[0][i].end(); h++)
{
pair<ll, int> it = *h;
for (int j = 0; j + i <= k; j++)
{
if (mp[1][j].count(s - it.first))
ans += (ll)it.second * mp[1][j][s - it.first];
}
}
}
printf("%I64d\n", ans);
return 0;
}