By 虎皮玄椒
488 字
2 分钟
Codeforces 525E
题目链接
题目大意
给定 个数, 次阶乘的机会,以及最终结果 。 每个数可以选择不选、选择原数、选择数的阶乘三个选项。但最多不能超过 个数选择阶乘。
要求出最终组合得到结果 的方案数。
题解
一开始以为是 dp 来的,但是 的范围太大了,,没有办法做 dp 。
用平常的搜索也会导致时间复杂度太高。
最后实在没办法了,去看了题解。用到了 meet-in-the-middle。没办法了,去看了题解。用到了 meet-in-the-middle。折半搜索。好吧说实话这是我学了之后第一次用这个方法,以至于我根本没有想到。
折半搜索适用于数据范围小,但是又不能直接使用暴搜的情况。
暴搜的复杂度通常是指数级的,而折半搜索则是把暴搜分为前后两个过程,这样可以把指数减半,即从 降低到 。
方法有了之后就通畅了。鉴于阶乘的增长速度,我们不需要计算很多阶乘, 就足够了。然后就是正常的 DFS,设定出界条件,以及接下来的 DFS。和普通 DFS 不同的是,折半搜索分为两部分,我们需要把第一部分的答案存起来,然后再在第二部分的搜索中借以求出最终答案。
例题
AC 代码
#include<bits/stdc++.h>#define endl "\n"#define int long longusing namespace std;int ppp[20];void init(){ int i=1; ppp[0]=1; for(;i<20;i++){ ppp[i]=ppp[i-1]*i; }}int n,k,s,ans;vector<int> v;vector<unordered_map<int,int>> m;void dfs1(int now,int end,int S,int used){ if(used>k)return; if(S>s)return; if(now>end){ m[used][S]++; return; } dfs1(now+1,end,S,used); dfs1(now+1,end,S+v[now],used); if(v[now]<=18)dfs1(now+1,end,S+ppp[v[now]],used+1);}void dfs2(int now,int end,int S,int used){ if(used>k)return; if(S>s)return; if(now>end){ for(int i=0;i<=k-used;i++) ans+=m[i][s-S]; return; } dfs2(now+1,end,S,used); dfs2(now+1,end,S+v[now],used); if(v[now]<=18)dfs2(now+1,end,S+ppp[v[now]],used+1);}signed main(){ std::ios::sync_with_stdio(false);std::cin.tie(0); init(); ppp[0]=0; cin>>n>>k>>s; m.resize(n+1); v.resize(n);for(int i=0;i<n;i++)cin>>v[i]; sort(v.begin(),v.end()); dfs1(0,n/2,0,0); dfs2(n/2+1,n-1,0,0); cout<<ans<<endl; return 0;}部分信息可能已经过时
