联赛模拟测试24 B. 答题 折半枚举

题目描述


分析

暴力的思想是把 (2^n) 种得分枚举出来,每一种得分的概率都是相同的,然后从小到大累加,直到大于等于所给的概率
把问题转化一下,就变成了在 (2^n) 种元素中求 (k) 小值
(n) 的范围是 (40)(2^{40}) 不可过,但是 (2^{20})可过
把序列分成两半,每一半的大小都是 (2^{n/2}),分别排序
二分 (k) 大值,在另一半中查找与当前这一半中某个元素的和恰好小于等于当前值的元素个数
因为元素大小具有单调性,所以二分没有必要,改成双指针
时间复杂度 (log(n imes m) imes 2^{n/2})

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#define rg register
typedef long long ll;
const int maxn=22;
int n,a[maxn<<1],bef[1<<maxn],lat[1<<maxn],tp1,tp2,zg;
ll k;
double p,now;
void dfs1(int now,int tot){
	if(now>n/2){
		bef[++tp1]=tot;
		return;
	}
	dfs1(now+1,a[now]+tot);
	dfs1(now+1,tot);
}
void dfs2(int now,int tot){
	if(now>n){
		lat[++tp2]=tot;
		return;
	}
	dfs2(now+1,a[now]+tot);
	dfs2(now+1,tot);
}
bool jud(int val){
	rg int now=tp2;
	rg ll ans=0;
	for(rg int i=1;i<=tp1;i++){
		while(lat[now]+bef[i]>val && now>0) now--;
		ans+=now;
	}
	return ans>=k;
}
int main(){
	scanf("%d%lf",&n,&p);
	for(rg int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		zg+=a[i];
	}
	now=1.0;
	for(rg int i=1;i<=n;i++){
		now=now*0.5;
	}
	k=(ll)std::ceil((double)p/now);
	dfs1(1,0);
	dfs2(n/2+1,0);
	std::sort(bef+1,bef+1+tp1);
	std::sort(lat+1,lat+1+tp2);
	rg int l=0,r=zg,mids;
	while(l<=r){
		mids=(l+r)>>1;
		if(jud(mids)) r=mids-1;
		else l=mids+1;
	}
	printf("%d
",l);
	return 0;
}
原文地址:https://www.cnblogs.com/liuchanglc/p/13879262.html