• 联赛模拟测试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;
    }
    
  • 相关阅读:
    BZOJ 1899: [Zjoi2004]Lunch 午餐
    BZOJ3670: [Noi2014]动物园
    BZOJ3712: [PA2014]Fiolki
    BZOJ1057: [ZJOI2007]棋盘制作
    BZOJ4326: NOIP2015 运输计划
    BZOJ4721: [Noip2016]蚯蚓
    BZOJ1131: [POI2008]Sta
    BZOJ1856: [Scoi2010]字符串
    BZOJ4003: [JLOI2015]城池攻占
    [AH2017/HNOI2017]单旋
  • 原文地址:https://www.cnblogs.com/liuchanglc/p/13879262.html
Copyright © 2020-2023  润新知