【[APIO/CTSC2007]动物园】状压DP
题目测评:https://www.luogu.org/problemnew/show/P3622
题目描述
新建的圆形动物园是亚太地区的骄傲。圆形动物园坐落于太平洋的一个小岛上,包含一大圈围栏,每个围栏里有一种动物。如下图所示:
你是动物园的公共主管。你要做的是,让每个来动物园的人都尽可能高兴。今天有一群小朋友来动物园参观,你希望能让他们在动物园度过一段美好的时光。但这并不是一件容易的事——有的动物有一些小朋友喜欢,有的动物有一些小朋友害怕。如,Alex 喜欢可爱的猴子和考拉,而害怕拥牙齿锋利的狮子。而Polly 会因狮子有美丽的鬃毛而喜欢它,但害怕有臭味的考拉。
你可以选择将一些动物从围栏中移走以使得小朋友不会害怕。但你不能移走所有的动物,否则小朋友们就没有动物可看了。
每个小朋友站在大围栏圈的外面,可以看到连续的 5 个围栏。你得到了所有小朋友喜欢和害怕的动物信息。当下面两处情况之一发生时,小朋友就会高兴:
-
至少有一个他害怕的动物被移走
-
至少有一个他喜欢的动物没被移走
例如,考虑下图中的小朋友和动物:
假如你将围栏 4 和 12 的动物移走。Alex 和 Ka-Shu 将很高兴,因为至少有一个他们害怕的动物被移走了。这也会使 Chaitanya 高兴,因为他喜欢的围栏 6 和8 中的动物都保留了。但是,Polly 和 Hwan 将不高兴,因为他们看不到任何他们喜欢的动物,而他们害怕的动物都还在。这种安排方式使得三个小朋友高兴。
现在,换一种方法,如果你将围栏 4 和 6 中的动物移走,Alex 和 Polly 将很高兴,因为他们害怕的动物被移走了。Chaitanya 也会高兴,虽然他喜欢的动物 6被移走了,他仍可以看到围栏 8 里面他喜欢的动物。同样的 Hwan 也会因可以看到自己喜欢的动物 12 而高兴。唯一不高兴的只有 Ka-Shu。
如果你只移走围栏 13 中的动物,Ka-Shu 将高兴,因为有一个他害怕的动物被移走了,Alex, Polly, Chaitanya 和 Hwan 也会高兴,因为他们都可以看到至少一个他们喜欢的动物。所以有 5 个小朋友会高兴。这种方法使得了最多的小朋友高兴。
输入输出格式
输入格式:
输入的第一行包含两个整数 N,C,用空格分隔。N 是围栏数(1≤N≤10 000),C是小朋友的个数(1≤C≤50 000)。围栏按照顺时针的方向编号为 1,2,3,…,N。
接下来的 C 行,每行描述一个小朋友,描述下面的形式给出:
E F L X1 X2 … XF Y1 Y2 … YL
其中:
E 表示小朋友可以看到的第一个围栏的编号(1≤E≤N),也就是说,小朋友可以看到的围栏为 E,E+1,E+2,E+3,E+4。注意,如果编号超过 N 将继续从 1 开始算。
如:当 N=14,E=13 时,小朋友可以看到的围栏为 13,14,1,2 和 3。 F 表示小朋友害怕的动物数。L 表示小朋友喜欢的动物数。
围栏 X1, X2, …, XF中包含小朋友害怕的动物。 围栏 Y1, Y2, …, YL中包含小朋友喜欢的动物。 X1, X2, …, XF, Y1, Y2, …, YL是两两不同的数,而且所表示的围栏都是小朋友可以看到的。
小朋友已经按照他们可以看到的第一个围栏的编号从小到大的顺序排好了(这样最小的E对应的小朋友排在第一个,最大的E对应的小朋友排在最后一个)。
注意可能有多于一个小朋友对应的 E 是相同的。
输出格式:
仅输出一个数,表示最多可以让多少个小朋友高兴。
输入输出样例
说明
第一个样例是题目描述中的例子,所有的 C=5 个小朋友都能高兴。第二个样
例是一个不能使得所有 C=7 个小朋友都高兴的例子。
题解
这道题是状态压缩dp的进阶,不会状压dp的小盆友可以去看这篇博客
https://www.cnblogs.com/mxrmxr/p/9799832.html
......
好了。相信大家都学会状压dp了,那我们继续。
首先,看到这题,我们怎么知道它是用状压dp呢?
因为这里对于每个小朋友只能看到相邻的五个动物,对于每个位置我们可以改变移不移走开不开心的状态量很小,所以我们考虑状压DP。
其次,我们要搞出一个数组love[i][j],表示在i这个位置设置看到的5个动物有没有被移走(j用状压表示,0被移走,1没被移走)可以使得该位置多少个小朋友开心。
这个预处理很重要,也不是很好想,它的重要度堪比状态转移。
然后,考虑f[i][j]表示i位置状态为s时的最多小朋友开心数。
我们首先枚举对于1的前四个(对于n的后四个),然后之后根据f[i-1]一直搞dp,决策往上第5个(不会对目前状态影响)移不移。
则f[i][j]可以由第i-1个围栏移走和不移走两种状态转移得来:
至于方程的话,建议大家自己推一推,推完后再看我的方程。
据某位dalao说,这么写的时间复杂度O(2的10次方×n)
为大家呈现一下方程:f[i][j]=MAX(f[i-1][(j&15)<<1],f[i-1][(j&15)<<1|1])+love[i][j];(就知道你没自己推)
s&15是什么意思呢?
它的意思是找的对于现在状态的后四个对于上一个状态的前四个,为什么是15自己算一下15的二进制就知道了。
最后ans在f[n][S<<1|1]和f[n][S<<1]里面找大的,这道题就差不多了。
代码:
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; int n,c,ans; int love[100005][38],f[100005][38]; int MAX(int a,int b){return a>b?a:b;} int main() { scanf("%d%d",&n,&c); for(int i=1;i<=c;i++) { int e,f,l; scanf("%d%d%d",&e,&f,&l); int like=0,hate=0; for(int j=1;j<=f;j++) { int x; scanf("%d",&x); x=(x-e+n)%n; like|=(1<<x); } for(int j=1;j<=l;j++) { int y; scanf("%d",&y); y=(y-e+n)%n; hate|=(1<<y); } for(int j=0;j<=31;j++) if((hate&j || ((31^j)&like))) love[e][j]++; } ans=0; for(int h=0;h<16;h++) { memset(f[0],128,sizeof(f[0])); f[0][h<<1]=0; for(int i=1;i<=n;i++) { for(int j=0;j<=31;j++) { f[i][j]=MAX(f[i-1][(j&15)<<1],f[i-1][(j&15)<<1|1])+love[i][j]; } } ans=MAX(ans,MAX(f[n][h<<1],f[n][h<<1|1])); } printf("%d",ans); }
谢谢大家!