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]++;
}
有了这个数组,转移就很明显了,直接套用状压转移套路即可:
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,也就是一个 “可行的解”。
这就是一个典型的例子,后面转移用的是前面的错解,导致了一系列的错误。
总结:考虑转移初始值的时候一定要考虑到错误的转移怎么避免,如果想得不是很明白,可以全部开最小值或最大值减少错误的可能性。

浙公网安备 33010602011771号