题解 P4799 【[CEOI2015 Day2]世界冰球锦标赛】

P4799 【[CEOI2015 Day2]世界冰球锦标赛】 (折半搜索)

part1 40points

暴力的40分是很好写的,直接搜就行

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
using namespace std;
const int maxn=1e6;
int n,m;
int a[maxn];
int ans=1;
void dfs(int x,int la){
	if(la-a[x]<0){
		return ;
	}
	ans++;
	for(int i=x+1;i<=n;i++){
		dfs(i,la-a[x]);
	}
	return ;
}
signed main(){
//	ios::sync_with_stdio(false);
//	freopen("a.in","r",stdin);
	cin>>n;
	cin>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		dfs(i,m);
	}
	cout<<ans;	
	return 0;
}
 

pasrt2 100points

我们来考虑100分的做法,很明显,暴力是无法处理这么大的数据的我们

来考虑对其进行优化,这里就要折半搜索了

折半搜索是针对暴力搜索的优化算法,本质上来说就是把搜索区间分为两

半,分开计算来优化时间复杂度,折半搜索的条件是分开搜索的结果可以

进行合并,针对此题,我们可以将查询区间一分为二, \(1- mid\) , \(mid+1 -n\)

分别进行暴力搜索,我们用 \(a,b\) 数组存储两次搜索的结果,即每个区间

符合条件的所有花费

最后我们将 \(a\) 数组从大到小排序,针对b里的每一个值 \(bi\),我们在a数组里查询第一个大于\(m- bi\)的数的下标,答案加上下标减一的值,为什么要这么做?针对有序的\(a\)数组,对于每一个\(bi\)

我们所查找的下标减1的值就是\(a\)数组中与\(bi\)相加\(<=m\)的数的个数,

所以也有这么多方案是符合题意的。

为什么要查找第一个大于\(m-bi\)的数的下标?因为小于其下标的每一个值

加上\(bi\)的值一定小于等于\(m\),但我们不知道a数组中是

否有\(m-bi\),所以需要查找第一个大于它的数的下标减一,最后把答

案加起来即可,可能听起来有点迷糊,直接看代码要清晰许多。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=5e6;
inline int read(){
	int f=1;
	int ret=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')
			f=-f;
		ch=getchar();
	}	
	while(ch<='9'&&ch>='0'){
		ret=ret*10+(ch^'0');
		ch=getchar();
	}
	return ret*f;
}
int n,m;
int cnt1;
int cnt2;
int a[maxn];
int b[maxn];
int v[maxn];
void dfs1(int id,int mx,int sum){
	if(sum>m){
		return ;
	} 
	if(id>mx){
		a[++cnt1]=sum;
		return ;
	}
	dfs1(id+1,mx,sum+v[id]);
	dfs1(id+1,mx,sum);
}
void dfs2(int id,int sum){
	if(sum>m){
		return ;
	}
	if(id>n){
		b[++cnt2]=sum;
		return ;
	}
	dfs2(id+1,sum+v[id]);
	dfs2(id+1,sum);
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		v[i]=read();
	}
	int mid=n>>1;
	dfs1(1,mid,0);
	dfs2(mid+1,0);
	sort(a+1,a+1+cnt1);
	int ans=0;
	for(int i=1;i<=cnt2;i++){
		ans+=upper_bound(a+1,a+1+cnt1,m-b[i])-a-1;
	}
	cout<<ans;
	return 0;
}

原本搜索的时间复杂度为 \(O(2^n)\)

优化为 \(O(2^\frac{n}{2})\)

但最后需要加上合并的时间复杂度

posted @ 2020-11-30 15:30  折翼的小鸟先生  阅读(124)  评论(0编辑  收藏  举报