首先这题的N和M都非常大,对它们状压是不可能的。因为每个位置只能看到5个,所以不妨对每个位置进行状压,每个位置用一个5位的二进制数表示这个位置所看到的5个格子[i,i+4]的安排方式。
那么首先在输入时计算出每个位置每种状态能使多少人高兴,用cnt[i,S]记录。
***注意输入的位置并不是在五个格子中间,不同于给的图,而是在五个格子的起点(巨坑...)。
接下来推一下状态转移方程。为了方便转移,我们让i号格子所管的前四个格子[i,i+3],由i-1号格子所管的后四个格子[i,i+3]转移而来,如下图:
这里有一点位运算的知识,(S&15)是为了把S的二进制第五位丢掉,“<<1”是为了往后移一位,让第一位空出来,决定第i-1位是否移走(“?”处)。
此时,不论i-1位移走与否,最终S是确定的(比如上图的10101确定,只是前面的“?1010”要取最大),前后格每个状态是完全独立的,那么只需在末尾加上一个 cnt[i][S]即可。
于是最终的状态转移方程:
还有一个细节,由于是一个环,i=1时,需要得到它的初始状态,可以通过枚举f[0]的S完成。
f[N]做完后相当于回到了f[0],所以状态必须与f[0]一致,ans就取f[N][S<<1],f[N][S<<1|1]里的最大值。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=10005;
int N,M,cnt[MAXN][(1<<6)],f[MAXN][(1<<6)];
char c;
void scan(int &x)
{
for(c=getchar();c<'0'||c>'9';c=getchar());
for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}
int main()
{
int i,j,fear,like,x,pos,S;
scan(N);scan(M);
for(i=1;i<=M;i++)
{
scan(pos);scan(fear);scan(like);
int t1=0,t2=0;
while(fear--)
{
scan(x);
x=(x+N-pos)%N;
t1|=(1<<x);
}
while(like--)
{
scan(x);
x=(x+N-pos)%N;
t2|=(1<<x);
}
for(j=0;j<32;j++)
if((j&t2)!=0 || ((31^j)&t1)!=0)
cnt[pos][j]+=1;
}
int ans=0;
for(S=0;S<16;S++) //枚举起始
{
for(j=0;j<32;j++) f[0][j]=-0x3f3f3f3f; //足够小就行
f[0][S<<1]=f[0][S<<1|1]=0;
for(i=1;i<=N;i++)
for(j=0;j<32;j++)
f[i][j]=max(f[i-1][(j&15)<<1],f[i-1][(j&15)<<1|1])+cnt[i][j];
ans=max(ans,max(f[N][S<<1],f[N][S<<1|1]));
}
cout<<ans;
return 0;
}