折半搜索【p4799】[CEOI2015 Day2]世界冰球锦标赛
Description
今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。
给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。
Input
第一行,两个正整数 \(N\) 和 \(M\)\((1 \leq N \leq 40,1 \leq M \leq 10^{18})\),表示比赛的个数和 Bobek 那家徒四壁的财产。
第二行,\(N\) 个以空格分隔的正整数,均不超过 \(10^{16}\),代表每场比赛门票的价格。
Output
输出一行,表示方案的个数。由于 \(N\) 十分大,注意:答案 \(\le 2^{40}\)
显然这个题直接dfs是过不去的\(O(2^n)\)
但是我们可以一半一半的搜,即折半搜索,复杂度可以降到\(O(2^{\frac{n}{2}})\)
所以我们取一个\(mid\),分别搜前半段和后半段。
然后合并答案的时候就需要令某一个数组变得有序,在其中找到最靠右的合法位置,直接累加即可。
这里用到了\(upper\)_\(bound\)
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#define R register
#define lo long long
using namespace std;
const int gz=1e6+6e5;
inline void in(R lo &x)
{
R int f=1;x=0;char s=getchar();
while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
while(isdigit(s)){x=x*10+s-'0';s=getchar();}
x*=f;
}
lo a[gz],b[gz],mon[42],ans,m;
int sum,cnt,n,mid;
void dfs(R int dep,R lo now)
{
if(now>m)return;
if(dep>mid)
{
a[++cnt]=now;
return;
}
dfs(dep+1,now+mon[dep]);
dfs(dep+1,now);
}
void dfss(R int dep,R lo now)
{
if(now>m)return;
if(dep>n)
{
b[++sum]=now;
return;
}
dfss(dep+1,now+mon[dep]);
dfss(dep+1,now);
}
int main()
{
scanf("%d%lld",&n,&m);
for(R int i=1;i<=n;i++)in(mon[i]);
mid=(n+1)/2;
dfs(1,0);dfss(mid+1,0);
sort(b+1,b+sum+1);
for(R int i=1;i<=cnt;i++)
ans+=upper_bound(b+1,b+sum+1,m-a[i])-b-1;
printf("%lld",ans);
}
除特殊声明外,本博客作品均由顾z创作。
未经博主允许,不得转载