题意:
一共有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;
}

京公网安备 11010502036488号