2012-2013 Petrozavodsk Winter Training Camp H. Temperature

初见安~啊题目好长……也不知道咋缩写 总之就是12年某次冬令营的题目吧。题目链接:CF gym 100162 H

2012-2013 Petrozavodsk Winter Training Camp H. Temperature_第1张图片

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
#include
#include
#include
#include
#include
#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——

你可能感兴趣的:(动态规划,状压,dp)