2012-2013 Petrozavodsk Winter Training Camp H. Temperature

初見安~啊題目好長……也不知道咋縮寫 總之就是12年某次冬令營的題目吧。題目鏈接:CF gym 100162 H

Sol:

題意有點繞,大概是這樣的:有n個人,有人做了作業有人沒做作業,沒做作業的人就會在自己做了作業的人裏面隨機選一部分把他們的答案取平均值(下取整)再隨機在區間[-x_i,x_i]中選一個加上。現在已知每個人的答案,問至少有多少人是做了作業的。

順着題意我們可以很容易想到一個暴力做法:枚舉做了作業的人的集合S,枚舉每個沒做作業的人j,枚舉每個S的子集,只要每個t_j都能在某個子集中得到那麼該方案就是合法的。最後我們把所有合法的方案中做了作業的人數取min就好了。複雜度

先不看複雜度。我們看黑體字(注意所在層次)——要判斷t_j是否能從某個集合中得到,即判斷是否:。其中A_s我們表示集合S的溫度平均值。現在這個不等式有s有j,我們嘗試把他們分開:看起來優美一些。所以我們可以再開一個數組B_(s,j)表示j是否可以從集合s中得到他的t_j。通過A數組預處理出B數組,複雜度

到現在我們的複雜度好像還是沒有降下來,因爲即使用了B數組我們也還是需要一個for去枚舉S的子集。也就是說我們應該還需要一個數組tmp_(s,j)表示j是否可以從s的子集中得到他的t_j。【亂取的名字可能看着不太舒服】直接寫的話大概是:
,其中s'是s的子集。這個遞推式有一個優化技巧就是可以變成:
,即枚舉s去掉某一位的1後的那個子集就可以了。可以畫一個關於s的二進制遞推關係圖感受一下,這樣是可以覆蓋到所有子集的。當然,因爲枚舉的是所有子集沒有包括s全集,所以一開始可以全部給tmp賦值B數組。

到這裏,A、B、tmp三個數組我們都可以通過預處理得到。但是tmp數組的複雜度因爲要枚舉s的每一位所以是的,這個題又有多組數據,一組數據跑4e8可能就給你卡沒了。所以現在要再扔一個n。既然B和tmp都是bool類型,那把j這一維狀壓進去,開成int類型不就好了?

所以再帶回到最開始的暴力思路上,判斷是否每個沒做作業的人在tmp裏面都可以抄到,這個題就沒了。複雜度

思維難度有點大,也不知道有什麼tag可以打,但是推一波下來還是挺舒服的。最後代碼實現的話其實可以把B數組和tmp合成同一個……

上代碼——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 22
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, a[maxn], T = 0, x[maxn];
int A[1 << maxn], B[1 << maxn], tmp[1 << maxn];
bool in(int a, int l, int r) {return l <= a && a <= r;}
signed main() {
	while(~scanf("%d", &n)) {
		T++;
		for(int i = 0; i < n; i++) a[i] = read(), x[i] = read();
		for(int i = 1; i < (1 << n); i++) A[i] = B[i] = tmp[i] = 0;//記得清空QAQ
		
		for(int i = 1; i < (1 << n); i++) {
			register int cnt = 0;
			for(int j = 0; j < n; j++) if(i & (1 << j)) cnt++, A[i] += a[j];
			A[i] /= cnt;//算平均值
			for(int j = 0; j < n; j++) if(!(i & (1 << j)) && in(A[i], a[j] - x[j], a[j] + x[j])) B[i] += (1 << j);
			tmp[i] = B[i];//因爲tmp遞推的時候是子集,所以全集要先直接賦值B
		}
		
		for(int i = 1; i < (1 << n); i++) for(int j = 0; j < n; j++) if(i & (1 << j))
			tmp[i] |= tmp[i - (1 << j)];//遞推
		
		int ans = n;
		for(int i = 1; i < (1 << n); i++) {
			register int cnt = 0, s = ((1 << n) - 1) ^ i;//s是沒做作業的集合
			if((tmp[i] & s) == s) {//每個都能在tmp裏面得到答案
				for(int j = 0; j < n; j++) if(i & (1 << j)) cnt++;
				ans = min(ans, cnt);
			}
		}
		printf("Case %d: %d\n", T, ans);
	}
	return 0;
}

迎評:)
——End——

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