题意:

一共有N个数字,我们可以最多使用K次魔法,一共有多少种方案使其和能够为S、

使用魔法的同时只能对一个数字使用,能够使其变成!ai,就是ai这个数的阶乘。

题解:

我们可以采用折半搜索的方法
就是处理前一半,把结果储存起来,再处理后一半,然后匹配前一半存储的结果。
放在本题上,我们记录前半区间的值和对应的k的使用情况,再记录后半区间的值,然后根据前半区间的情况看后半区间是否有对应情况
比如前半区间值为W,就看后半区间有几个S-W,因为要求值为S
当然W可以为0,也可以为S
这个方法也叫折半DFS
能降低复杂度

代码:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);
   return s*w;
}
ll p[30];
const int maxn=30;
struct node{
    int sum;
    int ksum;
}b[1200080];
int a[1200004];
int tot=0;
ll n,K,s;
map<ll,ll>mp[maxn];//用[i]次阶乘,和为[j]的方案数
void dfs(int cnt,int k,int sum,int end)
{
    //cnt为当前做选数的坐标 (cnt初始值为前边界) 
    //k表示所用魔法的数量
    //sum为当前所能表示的值
    //end表示后边界 
    if(sum>s||K<k)return ;

    if(cnt>end)
    {
        if(end==n/2)//左半区 
        {
            tot++;//左半区所能表达的数的数量 
            b[tot].ksum=k;
            b[tot].sum=sum;
        } 
        else if(end==n)//后半区 
        {
            mp[k][sum]++;//记录 后半区的值 
        }
        return ;
    }

    dfs(cnt+1,k,sum,end);//不选 
    dfs(cnt+1,k,sum+a[cnt],end);//选本身 
    if(a[cnt]<19)dfs(cnt+1,k+1,sum+p[a[cnt]],end);//选阶乘 
}
void init()
{
    p[1]=1;
    for(ll i=2;i<19;i++)p[i]=p[i-1]*i;
}
int main()
{

    cin>>n>>K>>s;
    for(int i=1;i<=n;i++)cin>>a[i];
    int mid=n/2;
    dfs(1,0,0,mid);
    dfs(mid+1,0,0,n);
    ll ans=0;
    for(int i=1;i<=tot;i++)
    {
        for(int k=0;k<=K-b[i].ksum;k++)
        {
            ans+=mp[k][s-b[i].sum];
        } 
    }
    cout<<ans;
    return 0;
}