世界冰球錦標賽[折半搜索]

特別鳴謝

由孟神糾正出的一個錯別字,現已更正。
特別提示:膜孟神能有特殊BUFF加成。

題解中涉及大佬的博客

梁神
園神

題目描述

譯自 CEOI2015 Day2 T1「Ice Hockey World Championship」
今年的世界冰球錦標賽在捷克舉行。Bobek 已經抵達布拉格,他不是任何團隊的粉絲,也沒有時間觀念。他只是單純的想去看幾場比賽。如果他有足夠的錢,他會去看所有的比賽。不幸的是,他的財產十分有限,他決定把所有財產都用來買門票。
給出 Bobek 的預算和每場比賽的票價,試求:如果總票價不超過預算,他有多少種觀賽方案。如果存在以其中一種方案觀看某場比賽而另一種方案不觀看,則認爲這兩種方案不同。

輸入格式

第一行,兩個正整數 N 和 M(1N40,1M1018)M(1 \leq N \leq 40,1 \leq M \leq 10^{18}),表示比賽的個數和 Bobek 那家徒四壁的財產。

第二行,N 個以空格分隔的正整數,均不超過 101610^{16} ,代表每場比賽門票的價格。

輸出格式

輸出一行,表示方案的個數。由於 NN 十分大,注意:答案 240\le 2^{40}

輸入輸出樣例

輸入

5 1000
100 1500 500 500 1000

輸出

8

說明/提示

樣例解釋
八種方案分別是:

一場都不看,溜了溜了
價格100的比賽
第一場價格500的比賽
第二場價格500的比賽
價格 100 的比賽和第一場價格 500的比賽
價格 100 的比賽和第二場價格 500的比賽
兩場價格 500的比賽
價格 1000 的比賽
有十組數據,每通過一組數據你可以獲得 10 分。各組數據的數據範圍如下表所示:

數據組號 1-21−2 3-43−4 5-75−7 8-108−10
NN \leq 10 | 20 | 40 |40
M10610181061018M \leq10^6|10^{18} | 10^6|10^{18}

這題在不是很相信洛谷上是紫題,畢竟很快就想出來了。
當然還是得益於大佬提點我的那句:爆搜。
機房有一大佬,一日與不知何處的小學弟QQ上交流,學弟給出一題:現有一揹包,揹包有一承重W
n個物品,物品有重量viv_i,求裝揹包的方案數 。
N40,M1018N\leq40, M\leq10^{18}
對,就是這道題改了下描述。
當日,大佬說題難度普及,要我與其同思其解,幫學弟解疑答惑。
我信了題目的鬼,深以爲是揹包dp,思良久不得解。
忽另一大佬路過,聽其題意後,戲笑而說:爆搜
吾思之,N若爲20,尚可枚舉,今有40,不可枚舉。
忽靈光一閃至,爲何不從兩邊搜,一邊搜20,複雜度豈不可過
搜兩次,統計方法答案爲何?
可若第二次搜完時枚舉第一次狀態判斷合法否,複雜度仍爲2402^{40},吾休矣。
這時大佬突然對我施以援手:記錄第一次搜的狀態,排序,二分查找
感激甚,大佬無愧於大佬,救人於水深火熱之中,如菩薩降臨,帶衆生脫離苦海。

好,
第一次搜時用a數組存下合法狀態,即每次搜完後合法的門票價錢和。
對存下的狀態進行遞增排序
第二次時,對於每一個搜完的合法狀態,我們記選擇的門票爲bb;前一次搜出的狀態爲aia_i
那麼和並狀態時,合併合法的條件是ai+bma_i + b \leq m 變一下就是aimba_i \leq m - b
我們剛剛對a數組進行了排序,那麼我們就可以找到第一個大於mbm-b的數,這個數前面的數都是合法的,
那麼合法的就有改數下標-1個,直接加到答案中去即可。
這樣的話我們的複雜度就是O(2n2+2n2log(2n2))O(2^{\frac{n}{2}} + 2^{\frac{n}{2}} log(2^{\frac{n}{2}}))
此題可過矣。
CodeCode

#include<bits/stdc++.h>

#define ll long long
#define MAXN 500010
#define N 201
#define INF 0x3f3f3f3f
#define gtc() getchar()

using namespace std;

template <class T>
inline void read(T &s){
	s = 0; T w = 1, ch = gtc();
	while(!isdigit(ch)){if(ch == '-') w = -1; ch = gtc();}
	while(isdigit(ch)){s = s * 10 + ch - '0'; ch = gtc();}
	s *= w;
}

template <class T>
inline void write(T x){
    if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x/10);
    putchar(x % 10 + '0');
}

int n;
ll m;
ll w[MAXN];
ll a[1 << 21], cnt = 0;
int mid;

void dfs(int x, ll ans){//枚舉前一半
	if(ans > m) return ;//剪枝
	if(x > mid){
		a[++cnt] = ans;
		return ;
	}
//	printf("%d %d\n", x , ans);
	dfs(x+1, ans);
	dfs(x+1, ans + w[x]);
}

ll ans = 0;
void dfs2(int x, ll num){//枚舉後一半
	if(num > m) return ;//剪枝
	if(x > n){//搜完後二分查找
		int tx = upper_bound(a+1, a+cnt+1, m - num) - a - 1;
		ans += tx;//累加答案
		return ;
	}
	
	dfs2(x+1, num);
	dfs2(x+1, num+w[x]);
}
int main()
{
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	read(n), read(m);
	mid = n >> 1;
	for(int i = 1; i <= n; ++i) read(w[i]);
	
	dfs(1, 0);
	sort(a + 1, a + cnt + 1);
	dfs2(mid + 1, 0);
	
	cout << ans << endl;
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章