1. 程式人生 > 實用技巧 >題解 P4799 【[CEOI2015 Day2]世界冰球錦標賽】

題解 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})\)

但最後需要加上合併的時間複雜度