洛谷P3622 [APIO2007]动物园 题解

该博客详细介绍了如何解决洛谷P3622 [APIO2007]动物园问题,通过分析得出小朋友的快乐只与他们看到的5个围栏有关,因此采用状态压缩DP方法。博主解释了状态定义、转移方程,并提供了预处理和初始化的策略。文章包含具体代码实现。

题目链接

分析:

因为小朋友是否高兴取决于动物的在或不在,很容易想到状压DP。看一眼数据范围: n ⩽ 1 0 4 , c ⩽ 5 × 1 0 4 n \leqslant 10 ^ 4,c \leqslant 5 \times 10 ^ 4 n104,c5×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 0S11111(2)=31,二进制下低位对应编号小的动物,对应位为 1 1 1则代表这个动物留下来,否则代表撤走。



考虑转移方程前,先思考一件事:当第 i i i位到第 i + 4 i + 4 i+4位的状态为 S S S时,从第 i − 1 i - 1 i1位开始的五位的状态 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 i1位了,当第 i − 1 i - 1 i1位为 0 0 0时,有 S ′ = ( S   &   15 )   < <   1 S' = (S\, \&\,15)\,<<\,1 S=(S&15)<<1;当第 i − 1 i - 1 i1位为 1 1 1时,则为 S ′ = ( S   &   15 )   < <   1   ∣   1 S' = (S\,\&\,15)\,<<\,1 \,|\,1 S=(S&15)<<11




于是就可以写转移方程了:
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(fi1,(S&15)<<1,fi1,(S&15)<<11)+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值