CodeForces 550B Preparing Olympiad(状态压缩,暴搜)

题目链接:http://codeforces.com/problemset/problem/550/B



【题目大意】


每组数据输入 n,l,r,x

然后输入n个数

从n个数中选出 m个 数  要求这m个数的和大于等于 l ,并且小于等于 r , 而且这m个数中 最大值 和最小值的差 不小于x

求有多少种选法。


【思路】


由于 n 的范围是 1~15 ,应该可以用暴力解决,,但是写不出来。。

后来学长讲了一个二进制状态压缩的方法,

一共n个数,我们把每个数看做一个开关,那么所有的可能一共有 2^n种 ,

从1 到2 ^n 对应了所有的可能 ,

如何判断这n个数中,哪个是选中的哪?

2^0 = 00001

2^1 = 00010

2^2 = 00100

2^3 = 01000

我们只要把每种可能 与 2的0 ~n 次方进行 位与操作 就可以判断了

这样全部枚举就能得出结果, n最大为15, 2^15小于 1e6 所以不会超时

【源代码】

#include <iostream>
#include <cstdio>
using namespace std;
int num[20];
int tmp[20];
const int INF = 0xfffffff;
int main(){
	int n,l,r,x;
	for(int i=1;i<=15;i++)
		tmp[i]=(1<<(i-1));
	while(scanf("%d%d%d%d",&n,&l,&r,&x)!=EOF){
		int count=0;
		for(int i=1;i<=n;i++) scanf("%d",&num[i]);
		for(int i=1;i<=(1<<n);i++){
			int maxx=-1,minn=INF,sum=0;
			for(int j=1;j<=15;j++){
				if(i&tmp[j]){
					sum+=num[j];
					maxx=max(maxx,num[j]);
					minn=min(minn,num[j]);
				}
			}
			if(sum>=l&&sum<=r&&maxx-minn>=x){
				count++;
			}
		}
		cout<<count<<endl;
	}
	return 0;
}

【补充一个搜索版本的】

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,l,r,x;
int num[20];
bool vis[20];
int maxx , minn;
int sum;
int ans;
void dfs(int po){
	if(sum>=l && sum<=r && maxx-minn>=x){
		ans++;
		// cout<<sum<<" "<<maxx<<" "<<minn<<endl;
	}
	for(int i=po;i<n;i++){
		if(!vis[i]){
			if(sum+num[i]<=r){
				int premax = maxx;
				if(num[i]>maxx){
					maxx = num[i];
				}
				int premin = minn;
				if(num[i]<minn)
					minn = num[i];
				sum+=num[i];
				vis[i] = true;
				dfs(i);  //回溯时,连当前的最大最小值也要回溯
				sum-=num[i];
				vis[i] = false;
				maxx = premax; //用premax来记录
				minn = premin;
			}
		}
	}
	
}
int main(){
	while(scanf("%d%d%d%d",&n,&l,&r,&x)!=EOF){
		memset(vis,0,sizeof(vis));
		sum = 0;
		ans = 0;
		for(int i=0;i<n;i++){
			scanf("%d",&num[i]);
		}
		maxx = -1;
		minn = 2000000000;
		if(n == 1 && l <= num[0] && r >= num[0] && x == 0){ //加了一个特判
			printf("1\n");
		}
		else{
			dfs(0); //从第一个数开始搜索
			printf("%d\n",ans);
		}
	}
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值