比较费脑子的一道题
先说题目核心思想 : 状压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; }