P3622 [APIO2007] 动物园 题解

本篇题解与其他题解的主要区别在于:详细解释了为什么数组赋初值的时候要赋特别小,如果发现自己WA了几个点可以来看一下本篇题解。(思路了解的同学可以直接跳到关于赋初值的部分)

了解题意后,可以发现最多有 \(10^4\) 个动物,那么想压动物是否被移走是显然会炸的,当然压小朋友更不可能,可是发现每位小朋友只能看到连续的五个动物,而这样只需要 32 的空间,是行得通的。

对此我们令 \(f_{j,s}\) 表示对于前 \(j\) 个动物,目前算到第 \(j\) 个(起始位置),的移走状态为 \(s\) 的最大高兴的小朋友数量。

可是单独只用这一个转移数组做起来会很费劲,那我们便再建一个数组 \(w\) 存储在区间 $\left [ i,i+4 \right ] $ 的动物移走状态为 \(s\) 高兴的小朋友的数量(因为此区间而高兴)

预处理代码:

cin >> n >> c;
for(int i = 1; i <= c; i++)
{
	fear = 0, love = 0;
	cin >> e >> F >> l;
	for(int j = 1; j <= F; j++)
	{
		cin >> x; 
		x = (x - e + n) % n;
		fear |= (1 << x);
	}
	for(int j = 1; j <= l; j++)
	{
		cin >> x;
		x = (x - e + n) % n;
		love |= (1 << x);
	}
	for(int j = 0; j < 32; j++) //00000 - 11111 表示动物是否被移走
		if((j & fear) || (~j & love))
			w[e][j]++;
}

有了这个数组,转移就很明显了,直接套用状压转移套路即可:

\[f_{j,s}=\max(f_{j-1,(s\&15)<<1},f_{j-1,(s\&15)<<1|1})+w_{j,s} \]

int ans = 0;
for(int i = 0; i < 32; i++)
{
	memset(f[0], -0x3f, sizeof f[0]); 
	f[0][i] = 0;
	for(int j = 1; j <= n; j++)
		for(int s = 0; s < 32; s++)
			f[j][s] = max(f[j - 1][(s & 15) << 1], f[j - 1][(s & 15) << 1 | 1]) + w[j][s];
	ans = max(ans, f[n][i]);
}
cout << ans;

以上就是本题思路,接下来讲解:

memset(f[0], -0x3f, sizeof f[0]); 

虽然本题的转移中没有进行任何的相减操作,不过还是要把数组赋得特别小,有可能有人认为是溢出,不过如果担心溢出,为什么不去开更大的数组而是去干一件不一定起作用的事?

不是溢出的问题,还能想到的是错解的问题,请大家思考这样的一种可能:

我们假设赋出的值是 -1。

(注:为了更好体现状态,将 \(s\) 转为二进制形式)

显然 \(f[0][00000]\) 无法转移到 \(f[1][00111]\)

所以 \(f[1][00111]\) 就会保持在 -1。

可是由于 \(f[2][01111]\) 可以从 \(f[1][00111]\) 转移到,且 \(w[2][01111]\) 等于 4,那么 \(f[2][01111]\) 的值就变成了 3,也就是一个 “可行的解”。

这就是一个典型的例子,后面转移用的是前面的错解,导致了一系列的错误。

总结:考虑转移初始值的时候一定要考虑到错误的转移怎么避免,如果想得不是很明白,可以全部开最小值或最大值减少错误的可能性。

posted @ 2022-10-06 15:13  iFear  阅读(34)  评论(0)    收藏  举报
Live2D