[APIO2007]动物园 状压DP

表示调这题调到失智了,,,调了好久发现是因为DP的方向反了。。。

首先我们观察到虽然动物和小朋友数量都很多,但是每个小朋友可以看到的动物只有连续的5个

因此我们考虑状压,2^5还是可以承受的。

再观察到因为是连续的5个,所以对于任意一段以i为开头的区间而言,区间都是已经被固定了的。

因此对于每个区间单独考虑。

f[i][j]表示以i为开头的区间,状态为j的最优解。

但是观察到,由于判断每个小朋友贡献的条件相对复杂,而且要临时处理不方便。

所以考虑预处理出任意以i为开头的区间的任意状态对应了几个小朋友满意

这里在读入每个小朋友的时候给对应区间每种状态累加上他的贡献即可

num[i][j]表示以i为开头的区间,状态为j时有多少个刚好看见这个区间的小朋友满意

于是有转移方程

now=j & 15;//j为当前状态

f[i][j]=max(f[i-1][(now << 1) | 1],f[i-1][now << 1]) + num[i][j];

转移方程怎么推?

注意到因为每个区间的贡献都是单独统计的,所以每个区间是相对独立的,但是仍然有限制,

懒得用各种编辑器其实是不会,还是手写吧

原谅我巨丑无比的字

然后再来考虑环如何处理,

发现因为前几位和后几位涉及到区间跨越的问题,很不好处理,所以考虑暴力枚举前5位,

同时f[1][j]将不统计上一个区间f[n][k]的答案(因为后面DP到那还会统计一次)

为了限制前几位只能取枚举到的k,

首先memset(f[1],128,sizeof(f[1]));//初始化为极小值

1后面的不用修改,因为DP过程中会改的。

f[1][k]=num[1][k];

然后从2开始DP

处理到后面几位的时候,又需要限制前几位只能是枚举到的k,

那怎么办?

特判?

其实有一个更妙的方法

我们创建一个虚拟区间n+1,这个区间由n转移而来,也就是说实质上就是对应的f[1],

因此f[n+1][k]就表示前5个取了k,

于是在统计答案的时候比较ans , f[n+1][k]即可

同时注意一下开始预处理num的时候对环的处理

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define R register int
 4 #define AC 10010
 5 #define getchar() *o++
 6 char READ[5000100],*o=READ;
 7 int n,m,like,fear,ans;
 8 int num[AC][35],f[AC][35],g[AC][35];
 9 /*num[i][j]表示从第i位开始,状态为j的情况下,将会有几个小朋友满意(区间完全吻合的)
10 f[i][j]表示从第i位开始,状态为j,最多有几个小朋友满意(总共的)
11 注意对环的处理?
12 既然一开始就预处理出了所有状态,,,那直接跑有何不可呢???*/
13 inline int read()
14 {
15     int x=0;char c=getchar();
16     while(c > '9' || c < '0') c=getchar();
17     while(c >= '0' && c <= '9') x=x*10+c-'0',c=getchar();
18     return x;
19 }
20 
21 void pre()//in && getnum
22 {
23     int a,b,c,d;
24     n=read(),m=read();
25     for(R i=1;i<=m;i++)
26     {
27         a=read(),b=read(),c=read();
28         like = fear = 0;
29         for(R j=1;j<=b;j++)//get fear
30         {
31             d=(read() - a + n) % n;//获取是看到的5个中的哪一个,不过因为只要移动d-1位,所以干脆在这里就不加1了
32             fear |= 1 << d;
33         }
34         for(R j=1;j<=c;j++)//get like
35         {
36             d=(read() - a + n) % n;//同理error!!!因为是环,所以有可能会减成负数
37             like |= 1 << d;
38         }
39         for(R j=0;j<32;j++)
40             if(((j & fear) != fear) || (j & like)) num[a][j]++;
41     }
42 }
43 //我错了QAQ,,,,根本不需要这么多特判,首先前几个的特判完全可以用初始化来代替
44 //而后几个的也不用,可以一直算到n+1,将n+1的状态限定在k,从n转移,这样满足条件了,不满足的会被筛掉
45 
46 void work()//getans
47 {//强行储存第一位并不能保证最优,所以还是要强行枚举前5个的状态,保证后面状态一定可以取到最优
48     for(R k=0;k<32;k++)//枚举前5个的状态
49     {
50         memset(f[1],128,sizeof(f[1]));
51         int a=n+1;
52         f[1][k]=num[1][k];
53         for(R i=2;i<=a;i++)
54         {
55             for(R j=0;j<32;j++)
56             {
57                 int now=j & 15;
58                 f[i][j]=max(f[i-1][(now << 1) | 1],f[i-1][now << 1]) + num[i][j];
59             }//error!!!!这里不要max(f[i][j],)因为f[i][j]本来就只要取一次,而且又没有初始化!!!
60         }
61         ans=max(ans,f[n+1][k]);
62     }
63     printf("%d\n",ans);
64 }
65 
66 int main()
67 {
68 //    freopen("in.in","r",stdin);
69     fread(READ,1,5000000,stdin);
70     pre();
71     work();
72 //    fclose(stdin);
73     return 0;
74 }

猜你喜欢

转载自www.cnblogs.com/ww3113306/p/9088124.html