分析:
因为小朋友是否高兴取决于动物的在或不在,很容易想到状压DP。看一眼数据范围: n ⩽ 1 0 4 , c ⩽ 5 × 1 0 4 n \leqslant 10 ^ 4,c \leqslant 5 \times 10 ^ 4 n⩽104,c⩽5×104,似乎又不太可能。但经过仔细观察,我们注意到一句话:
每个小朋友站在大围栏圈的外面,可以看到连续的 5 5 5 个围栏
发现每个小朋友是否高兴仅与他所能看到的 5 5 5个围栏有关,而 5 5 5非常小,可以拿来压缩!
那么我们可以设计出状态:令
f
i
,
S
f_{i,S}
fi,S表示从第
i
i
i位开始的五个动物是否留下的状态为
S
S
S时前
i
+
4
i + 4
i+4个动物最多能使多少小朋友高兴,其中
0
⩽
S
⩽
1111
1
(
2
)
=
31
0 \leqslant S \leqslant 11111_{(2)} = 31
0⩽S⩽11111(2)=31,二进制下低位对应编号小的动物,对应位为
1
1
1则代表这个动物留下来,否则代表撤走。
考虑转移方程前,先思考一件事:当第 i i i位到第 i + 4 i + 4 i+4位的状态为 S S S时,从第 i − 1 i - 1 i−1位开始的五位的状态 S ′ S' S′可能是什么?
因为第 i i i位到第 i + 3 i + 3 i+3这四位的状态是共有的,也是确定的,所以我们可以先把这四位处理出来: S & ( 0111 1 ( 2 ) = 15 ) S \& (01111_{(2)} = 15) S&(01111(2)=15),这样可以直接把第 i + 4 i + 4 i+4位处理掉,又因为第 i i i位到第 i + 3 i + 3 i+3这四位在 S ′ S' S′中是后四位,所以我们还需要左移一位: ( S & 15 ) < < 1 (S\,\&\,15)\,<<\,1 (S&15)<<1,这里要注意位运算的优先级,然后就是讨论第 i − 1 i - 1 i−1位了,当第 i − 1 i - 1 i−1位为 0 0 0时,有 S ′ = ( S & 15 ) < < 1 S' = (S\, \&\,15)\,<<\,1 S′=(S&15)<<1;当第 i − 1 i - 1 i−1位为 1 1 1时,则为 S ′ = ( S & 15 ) < < 1 ∣ 1 S' = (S\,\&\,15)\,<<\,1 \,|\,1 S′=(S&15)<<1∣1。
于是就可以写转移方程了:
f
i
,
S
=
m
a
x
(
f
i
−
1
,
(
S
&
15
)
<
<
1
,
f
i
−
1
,
(
S
&
15
)
<
<
1
∣
1
)
+
g
i
,
S
f_{i,S} = max(f_{i - 1,(S\,\&\,15)\,<<\,1},f_{i - 1,(S \,\&\,15) \,<< \,1\,|\,1}) + g_{i,S}
fi,S=max(fi−1,(S&15)<<1,fi−1,(S&15)<<1∣1)+gi,S
其中 g i , S g_{i,S} gi,S表示从第 i i i位开始的五个动物是否留下的状态为 S S S时能使多少在第 i i i位的小朋友高兴,显然可以预处理出来
写的时候要注意初始化问题,我们要选择一个起点并枚举从起点开始的五位的状态,并把当前枚举到的状态设为0(最后再次循环到起点时再计算这五位的贡献,方便计算),其它状态全部设为负无穷。考虑到起点会循环到两遍,为了防止下标取模出现问题、简化代码,可以选择第 1 1 1位的前一位(即 n n n)为起点,这样初始化时第一维就可以选择 0 0 0( 0 0 0在 1 1 1的前一位,而转移时都是由前一位更新后一位),循环时也就只会从 1 1 1到 n n n,不存在取模问题了。
详见代码
Code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 5e4 + 50,maxs = 31,inf = 1e9;
int n,m,a,b,c,t,p,q,ans,f[maxn][maxs],g[maxn][maxs];//m即为题目中的c
int read(){
int x = 0;
char c = getchar();
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + (c ^ 48),c = getchar();
return x;
}
int main(){
n = read(),m = read();
for(int i = 1; i <= m; i ++){//预处理出g
a = read(),b = read(),c = read(),p = 0,q = 0;//p为当前小朋友害怕的动物的状态,q为喜欢的
for(int j = 1; j <= b; j ++) t = (read() - a + n) % n,p |= (1 << t);//t为当前动物与小朋友的相对位置,下同
for(int j = 1; j <= c; j ++) t = (read() - a + n) % n,q |= (1 << t);
for(int j = 0; j <= maxs; j ++) if((~j & p) || (j & q)) g[a][j] ++;
//如果p中有j没有的动物或者q中有j中有的动物,则状态为j时当前小朋友开心,g[a][j]应++
}
for(int P = 0; P <= maxs; P ++){//枚举第n位开始的五位的状态
for(int S = 0; S <= maxs; S ++) f[0][S] = -inf;
f[0][P] = 0;//只有f[0][P]能达到
for(int i = 1; i <= n; i ++)
for(int S = 0; S < maxs; S ++)
f[i][S] = max(f[i - 1][(S & 15) << 1],f[i - 1][(S & 15) << 1 | 1]) + g[i][S];
ans = max(ans,f[n][P]);//统计最大值
}
cout << ans << endl;
return 0;
}
该博客详细介绍了如何解决洛谷P3622 [APIO2007]动物园问题,通过分析得出小朋友的快乐只与他们看到的5个围栏有关,因此采用状态压缩DP方法。博主解释了状态定义、转移方程,并提供了预处理和初始化的策略。文章包含具体代码实现。

389

被折叠的 条评论
为什么被折叠?



