Luogu [P3622] [APIO2007]动物园

题目链接

比较费脑子的一道题

先说题目核心思想 : 状压dp

环的处理我们先不管。

我们设 dp[j][s] 表示 到达动物 j 且 [ j , j+5) 这五个动物状态为s时 最多能使多少小朋友开心。

其中,s为 0~31 的整数,二进制下的s表示[ j , j+5) 这五个动物状态,0表示不选,1表示选,特别注意,[ j , j+5) 分别对应s从右往左数的每个数字,例如s = 18 ,二进制下表示为10010 ,即 j 到 j+4 的状态为 0 , 1 , 0 , 0 , 1。

可得状态转移方程:dp[j][s] = min( dp[j-1][(s&15)<<1] , dp[j-1][(s&15)<<1|1] ) + num[j][s];

解释:15 的二进制为01111,(s&15) 即取 [ j , j+5) 的前 4 个数的状态。然后 <<1 代表作为 [j-1 , j+4)的后四个状态,对j-1的状态枚举一下是0还是1,取个min,再加上num[j][s]就行了

num[j][s]为已经预处理出来的数组,它的意思为:

状态为s时,某几个视野为[j , j+5)的小朋友中高兴地人数

还是有点懵?没事,将上面的解释结合下图来看:

 

当前 j=3 , s=18 (10010) , s&5=2 (0010)
动物:             1    2    3    4    5    6    7    8    9    10
下标:                 j-1   j   j+1  j+2  j+3  j+4
s状态:                      0    1    0    0    1
(s&5)为:                    0    1    0    0
(s&5)>>1状态:          0    0    1    0    0
(s&5)>>1|1状态:        1    0    1    0    0

这样就比较好理解了。

num的预处理

可随读入一起处理:

 

for(int i=1;i<=m;i++){
        int E=read(),F=read(),L=read(),fear=0,like=0;
        for(int j=1;j<=F;j++){
            int x=read() ;
            x=(x-E+n)%n;
            fear|=(1<<x);//fear表示这五个围栏中这个小朋友害怕的动物的状态 
        }
        for(int j=1;j<=L;j++){
            int x=read();
            x=(x-E+n)%n;
            like|=(1<<x);//like表示这五个围栏中这个小朋友喜欢的动物的状态 
        }
        for(int j=0;j<32;j++)//0-> 拿走 1->留下 
            if((fear&~j)||(like&j))
                num[E][j]++;
    }

 

较难理解的几点:1.x = (x-E+n)%n,fear |= (1<<x)

这个为环的处理,手推几个式子就能得出

2.(fear&~j)||(like&j)

看题目条件:

  • 至少有一个他害怕的动物被移走

  • 至少有一个他喜欢的动物没被移走

 

这里留给读者自行思考,不再解释( '~'符号为取反,即0变1,1变0

然后就是最后dp环的处理:

可以枚举开始的状态 ,设开始0的状态为 i 则最后的状态只有 dp[n][i] 对答案有效,强制和开始的状态一样

OK结束,代码:

 

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
int n,m,ans;
int num[50005][35];
int dp[10005][35];
inline int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')
        ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x;
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){
        int E=read(),F=read(),L=read(),fear=0,like=0;
        for(int j=1;j<=F;j++){
            int x=read() ;
            x=(x-E+n)%n;
            fear|=(1<<x);
        }
        for(int j=1;j<=L;j++){
            int x=read();
            x=(x-E+n)%n;
            like|=(1<<x);
        }
        for(int j=0;j<32;j++)//0-> 拿走 1->留下 
            if((fear&~j)||(like&j))
                num[E][j]++;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<32;j++){
            cout<<num[i][j]<<" ";
        }
        cout<<endl;
    }
    for(int i=0;i<32;i++){
        memset(dp[0],128,sizeof(dp[0]));
        dp[0][i]=0;
        for(int j=1;j<=n;j++)
          for(int s=0;s<32;s++)
            dp[j][s]=max(dp[j-1][(s&15)<<1],dp[j-1][(s&15)<<1|1])+num[j][s];
        if(ans<dp[n][i])//开始状态为 i 结尾状态也得是i才能更新ans 
          ans=dp[n][i];
    }
    cout<<ans;
    return 0;
}

 

posted @ 2019-10-22 15:00  青珹  阅读(140)  评论(0编辑  收藏
Live2D