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; }