ZOJ3802 Easy 2048 Again (状压DP)

ZOJ Monthly, August 2014 E题

ZOJ月赛 2014年8月 E题

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5334

Easy 2048 Again

Time Limit: 2 Seconds      Memory Limit: 65536 KB

Dark_sun knows that on a single-track road (which means once he passed this area, he cannot come back again), there are some underground treasures on each area of the road which has the value of 2, 4, 8 or 16. Dark_sun can decide whether to dig them or not in order to get the highest score. The calculation rule of the total score is similar to the game Flappy 2048.

Dark_sun's bag is like a queue. When he gets a treasure, the treasure will be pushed back into the end of the bag. And the score will add the value of the treasure. For example, when there are treasures on the road in the order of {2, 8, 4, 8} and if Dark_sun decides to dig all of them, he will get the final score of 2+8+4+8=22. And his bag will finally become the sequence of {2, 8, 4, 8}.

If the adjacent treasures in the Dark_sun's bag have the same value, they will mix into a bigger treasure which has the value of their sum (the double value of the previous one). And Dark_sun will get a combo score of the value of bigger treasure. For example in the previous case, if Dark_sun decides to dig only the {2, 8, 8} treasure in sequence. He will get the basic score of 18(2+8+8). And when the last treasure (value 8) is pushed back into his bag, his bag will turn {2, 8, 8} into {2, 16} and he will get a bonus score of 16. And his final score will become 18+16=34 (which is the best strategy in this case.)

Notice that the treasures mix to the bigger one automatically when there are the same adjacent treasures. For example, when there are treasures of {2, 2, 4, 8, 16} on the road, and if Dark_sun decides to dig all of them, he will get the basic score of 32(2+2+4+8+16) and a bonus score of 60(4+8+16+32). At last he will get the total score of 92 and the bag becomes {32}.

Now, Dark_sun wants to get the highest score (no matter what's the treasure in his bag), can you tell him the what's the highest score?

Input

The first line is an integer n, which is the case number. In each case, the first line is an integer L, which is the length of the road.(0 < L ≤ 500) The second line contains L integers which can only be 2, 4, 8 or 16. This means the value of treasure on each area of the road.

Output

For each case, you should output an integer. The answer is the maximum of the total score which Dark_sun may get.

Sample Input
3
4
2 8 4 8
5
2 2 4 8 16
8
8 4 4 2 8 4 2 2
Sample Output
34
92
116
Hint

In the third sample case, Dark_sun will choose {8,4,4,8,4,2,2}. Firstly, the first three treasures will be combined to 16 and then the {16,8,4,2,2} will become 32. And he will get the basic score 32(8+4+4+8+4+2+2) and the bonus score 84(8+16+4+8+16+32).


Author: Ni, Xinyi Source: ZOJ Monthly, August 2014

题意:类似“Flappy 2048”,输入N个元素,元素是2 4 8 16四个数之一。从左到右飞,遇到一个数可以选择取或不取,取的话存到背包里(入栈),若背包里有相邻的相同的数字,会立刻自动合成他们的和。得分是取一个数可以得到这个数数值的分,合成一个数可以得到这个数数值的分,求最高总分。(N<=500)

题解:状压DP。

N<=500,500个16合起来能得8000,2^13=8192,所以最多只能合到4096。2,4,8......4096,即2^1~2^12,只有12种数。思考如何得到最优解,对一个数是否加入背包,是和背包中的若干个元素有关的,不只和最后一个元素有关(因为可能会连环合,例如背包里是8 4 2,现在加入一个2,啪啪啪就合成16了)。所以我们要把背包里的若干个元素也考虑进去,想成一个状态。但是背包里所有元素都考虑的话,有12^500种状态,简直尿,所以再想想。

和新加入的数有关的背包里的元素,其实就是最后的递减序列,因为只有递减的才可能因为新加入的数而合成新数,如果出现一个递增的,之前的数就都没办法再合成了,可以不用管,不用算进状态里。例如8 4 2 4,其中8 4 2就不可能再合并了,可能合并的数只有4,我们只用把4记进状态里;再例如1024 2 32 16 8 4 2,我们也只用记32 16 8 4 2就行。

这样,我们想办法用小空间存储递减序列的必要信息,可以用类似集合的存法,就是每个二进制位代表一个元素是否在集合中。

例如第1位代表是否有2,第2位代表是否有4,第3位代表是否有8……第12位代表是否有4096。这样,只用12位二进制数就能表示这个递减序列的信息(因为是递减序列嘛,顺序已经定了,只用存是否存在就行)。状态就可以用0~4095表示出来。

观察如何状态转移,也就是合并规则。

不加入当前元素,直接f[i][j]=max( f[i][j] , f[i-1][j])

加入当前元素:当递减序列里没有比当前元素小的元素,才可以合并;当递减序列里有比当前元素小的,就不能合并,而且新加入的元素作为新的递减序列(新的递减序列只包括这一个元素了)。

具体动规代码(滚动数组):

 1     int now=0,pre=1;
 2     mf1(f);
 3     f[pre][0]=0;
 4     for(i=1; i<=n; i++) {
 5         for(j=0; j<4096; j++) {
 6             if(f[pre][j] != -1) {
 7                 f[now][j]=max(f[now][j],f[pre][j]);///不选当前点
 8                 
 9                 int t=j;///状态
10                 k=a[i]-1;///a[i]代表的位的位置,第k位
11                 int q=(1<<k) -1 ;///q为( 比k低的位数全是1 )的数
12                 int get=p2[a[i]];///新得分
13                 if((t&q)==0) {///如果比k低的位全是0,才能合并
14                     while( (t & (1<<k)) ) {///合并,数之前有多少个连续的1,统计合并得分
15                         get+=p2[k+2];
16                         k++;
17                     }
18                     q=(1<<k) -1;
19                     t&=~q;///把比k低的位数全变成0,怕不怕
20                     t|=1<<k;
21                 }else t=1<<k;///不能合并,产生新递减序列,只有k位是1
22                 f[now][t]=max( f[now][t] , f[pre][j]+get );
23             }
24         }
25         swap(now,pre);
26     }
View Code

最后找f[n][j]的最大值就行了。

 

(怕了,比赛的时候直接看样例,没看题,以为是一维的2048,不会自动合并,我可以选后面的先合……逗乐!居然是Flappy2048,谁会玩那种逗游戏啊!看来以后还是要看题……然后我也没想到状压,其实还是挺简单的,只是我状压做得少/.\)

(这题不滚动数组,用[501][4096]也不会MLE,但是时间一秒六……大概数据组数多,清零耗时太多。滚动数组只要100ms左右)

全代码(滚动数组)

 1 //#pragma comment(linker, "/STACK:102400000,102400000")
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<iostream>
 5 #include<cstring>
 6 #include<algorithm>
 7 #include<cmath>
 8 #include<map>
 9 #include<set>
10 #include<stack>
11 #include<queue>
12 using namespace std;
13 #define ll long long
14 #define usll unsigned ll
15 #define mz(array) memset(array, 0, sizeof(array))
16 #define mf1(array) memset(array, -1, sizeof(array))
17 #define minf(array) memset(array, 0x3f, sizeof(array))
18 #define REP(i,n) for(i=0;i<(n);i++)
19 #define FOR(i,x,n) for(i=(x);i<=(n);i++)
20 #define RD(x) scanf("%d",&x)
21 #define RD2(x,y) scanf("%d%d",&x,&y)
22 #define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
23 #define WN(x) prllf("%d\n",x);
24 #define RE  freopen("D.in","r",stdin)
25 #define WE  freopen("1biao.out","w",stdout)
26 #define mp make_pair
27 #define pb push_back
28 const int maxn=511;
29 int p2[14];
30 
31 int n;
32 int a[maxn];
33 
34 int f[2][4096];///f[i][j],j为状态,集合,j二进制第i位为1表示存在元素2^i。
35 ///最多合出4096,是2^12,所以开12位来存,2^12=4096。
36 int farm() {
37     int i,j,k;
38     int now=0,pre=1;
39     mf1(f);
40     f[pre][0]=0;
41     for(i=1; i<=n; i++) {
42         for(j=0; j<4096; j++) {
43             if(f[pre][j] != -1) {
44                 f[now][j]=max(f[now][j],f[pre][j]);///不选当前点
45                 
46                 int t=j;///状态
47                 k=a[i]-1;///a[i]代表的位的位置,第k位
48                 int q=(1<<k) -1 ;///q为( 比k低的位数全是1 )的数
49                 int get=p2[a[i]];///新得分
50                 if((t&q)==0) {///如果比k低的位全是0,才能合并
51                     while( (t & (1<<k)) ) {///合并,数之前有多少个连续的1,统计合并得分
52                         get+=p2[k+2];
53                         k++;
54                     }
55                     q=(1<<k) -1;
56                     t&=~q;///把比k低的位数全变成0,怕不怕
57                     t|=1<<k;
58                 }else t=1<<k;///不能合并,产生新递减序列,只有k位是1
59                 f[now][t]=max( f[now][t] , f[pre][j]+get );
60             }
61         }
62         swap(now,pre);
63     }
64     int re=0;
65     for(j=0; j<4096; j++) {
66         re=max(re,f[pre][j]);
67     }
68     return re;
69 }
70 
71 int l2[17];
72 int main() {
73     int T,i,x;
74     l2[2]=1;
75     l2[4]=2;
76     l2[8]=3;
77     l2[16]=4;///l2[i]=log2(i)
78     p2[0]=1;
79     for(i=1; i<=13; i++)///最多就把500个16合在一起(其实不到),8000,2^13=8192
80         p2[i]=p2[i-1]*2;///p2[i]=pow(2,i)
81     scanf("%d",&T);
82     while(T--) {
83         scanf("%d",&n);
84         for(i=1; i<=n; i++) {
85             scanf("%d",&x);
86             a[i]=l2[x];
87         }
88         printf("%d\n",farm());
89     }
90     return 0;
91 }
View Code

 

代码FAQ(回答别人问题时写的,可以帮助理解)

  1 ///提问者 9:58:39 
  2 你把思路说一遍吧
  3 ///提问者 9:59:27 
  4 合并不是要相等的才合并吗
  5 "我的回答" 9:59:31 
  6 f[i][j],记第1到第i个元素中选若干个,达到状态j时的得分
  7 "我的回答" 9:59:35 
  8 对啊
  9 ///提问者 10:00:02 
 10 状态j是什么状态啊
 11 "我的回答" 10:00:16 
 12 状态是二进制各位表示是否有可以合并的2,是否有可以合并的4,是否有8……是否有可以合并的4096
 13 ///提问者 10:00:18 
 14 为什么递减就能合并,
 15 "我的回答" 10:00:39 
 16 比如j=二进制的0000000111,就说明有2和4和8
 17 "我的回答" 10:01:03 
 18 因为如果某次递增了,比如队列是8 4 2 4
 19 "我的回答" 10:01:10 
 20 那之前的8 4 2就不可能再合并了
 21 "我的回答" 10:01:34 
 22 可能合并的只有4,状态就是0000010
 23 ///提问者 10:02:45 
 24 没了啊
 25 "我的回答" 10:02:58 
 26 双击查看原图有没有理解
 27 ///提问者 10:04:01 
 28 那么怎么表示有8 4 2 4的队列呢
 29 ///提问者 10:05:41 
 30 还有怎么表示它加还是不加元素呢
 31 "我的回答" 10:05:55 
 32 这些在状态转移里
 33 "我的回答" 10:06:01 
 34 世界上我们是每个状态都看一下
 35 "我的回答" 10:06:12 
 36 初始的时候f[0][0]为0,其他都为-1,表示没有那种状态。
 37 "我的回答" 10:06:36 
 38 然后刚开始看8加入不加入,加入或者不加入我们都算出新的状态
 39 "我的回答" 10:06:54 
 40 不加入的话,f[1][0]=f[0][0]=0,得分还是0
 41 "我的回答" 10:07:13 
 42 加入的话,递减序列集合里只有{8},那状态就是00000100
 43 "我的回答" 10:07:38 
 44 二进制的100 = 十进制的4
 45 "我的回答" 10:07:50 
 46 f[1][4] = f[0][0]+8 = 8
 47 ///提问者 10:08:50 
 48 能在麻烦一下,说一下案例8 4 4 2 8 4 2 2是怎么转移的吗
 49 "我的回答" 10:09:19 
 50 我来用程序看一看
 51 ///提问者 10:14:12 
 52 得分是怎么加的。
 53 "我的回答" 10:16:39 
 54 
 55 i=8, j=9, k=1, t=10, get=6, f[i-1][j]=42, newscore=48
 56 i=8, j=10, k=0, t=11, get=2, f[i-1][j]=48, newscore=50
 57 i=8, j=11, k=2, t=12, get=14, f[i-1][j]=46, newscore=60
 58 i=8, j=12, k=0, t=13, get=2, f[i-1][j]=48, newscore=50
 59 i=8, j=13, k=1, t=14, get=6, f[i-1][j]=50, newscore=56
 60 i=8, j=14, k=0, t=15, get=2, f[i-1][j]=52, newscore=54
 61 i=8, j=15, k=4, t=16, get=62, f[i-1][j]=54, newscore=116
 62 i=8 j=16(10) f[i][j]=116
 63 i=7 j=15(f) f[i][j]=54
 64 i=6 j=14(e) f[i][j]=52
 65 i=5 j=12(c) f[i][j]=48
 66 i=4 j=8(8) f[i][j]=40
 67 i=3 j=8(8) f[i][j]=40
 68 i=2 j=6(6) f[i][j]=12
 69 i=1 j=4(4) f[i][j]=8
 70 116
 71 
 72 那个案例是由最后那8行所说的状态得来的,上面还算了每种状态
 73 "我的回答" 10:16:59 
 74 得分是如果没合成就直接加,合成了的话就多加合成的分
 75 "我的回答" 10:17:46 
 76 
 77                 int get=p2[a[i]];
 78                 if((t&q)==0) {///比k低的位全是0
 79                     while( (t & (1<<k)) ) {
 80                         get+=p2[k+2];
 81                         k++;
 82                     }
 83                     q=(1<<k) -1;
 84                     t&=~q;///把比k低的位数全变成0,怕不怕
 85                     t|=1<<k;
 86                 }else t=1<<k;///只有k位是1
 87                 int newscore=f[i-1][j]+get;
 88 
 89 这个是算合成得分的
 90 ///提问者 10:18:09 
 91 那怎么知道是否合成,就算是递减也不能怎么合成
 92 "我的回答" 10:18:16 
 93 p2[a[i]]是当前这个数的分
 94 ///提问者 10:18:23 
 95  get+=p2[k+2];?
 96 "我的回答" 10:18:36 
 97 这个p2[k+2]是pow(2,k+2)
 98 "我的回答" 10:18:43 
 99 就是合成这一次的得分
100 "我的回答" 10:18:55 
101 是否合成可以看状态
102 "我的回答" 10:19:11 
103 比如状态里得知递减序列里有8 4 2
104 ///提问者 10:19:15 
105 哪种状态可以合成
106 "我的回答" 10:19:21 
107 那新加入的2就可以合成4,然后合成8,然后合成 16
108 "我的回答" 10:19:40 
109 状态是8 4 2,新加入的是4的话,就不能合成
110 ///提问者 10:20:53 
111 这个p2[k+2]是pow(2,k+2)这个不理解
112 "我的回答" 10:21:15 
113 居然,这个是我预处理的
114 "我的回答" 10:21:30 
115 先算好了2^k,存到p2[k]里
116 "我的回答" 10:22:01 
117 然后我状态是每位存是否有某个数嘛,第一位为1说明有2,第二位为1说明有4
118 "我的回答" 10:23:17 
119 然后如果新加入的数是8
120 "我的回答" 10:23:32 
121 8是存在第三位的,我们要看第一位第二位是否是1,是1的话就说明有比8小的数
122 "我的回答" 10:23:34 
123 就不能合成
124 "我的回答" 10:25:25 
125 如果都为0,说明能合成,然后看第三位,为1的话就说明有8,能合成,合完了看第四位,就是看有没有16,这样一直合下去
126 ///提问者 10:27:08 
127 哪里体现了8是存在第三位的
128 "我的回答" 10:27:37 
129         scanf("%d",&n);
130         for(i=1; i<=n; i++) {
131             scanf("%d",&x);
132             a[i]=l2[x];
133         }
134 "我的回答" 10:27:43 
135 一开始我是这样读的嘛
136 "我的回答" 10:27:52 
137 l2[x]是log2(x)
138 "我的回答" 10:28:02 
139 l2[8]就是3
140 "我的回答" 10:28:13 
141 然后我就把2 4 8 16变成了1 2 3 4
142 ///提问者 10:29:49 
143 在状态转移的过程中哪里知道是否有相同的存在
144 "我的回答" 10:30:16 
145                     while( (t & (1<<k)) ) {
146                         get+=p2[k+2];
147                         k++;
148                     }
149 "我的回答" 10:30:27 
150 t是状态
151 "我的回答" 10:31:09 
152 t & (1<<k)为真,说明t的第(k+1)位为真
153 "我的回答" 10:31:36 
154 就说明有第(k+1)位代表的数,就能知道能不能合了
155 ///提问者 10:27:08 
156 哪里体现了8是存在第三位的
157 "我的回答" 10:27:37 
158         scanf("%d",&n);
159         for(i=1; i<=n; i++) {
160             scanf("%d",&x);
161             a[i]=l2[x];
162         }
163 "我的回答" 10:27:43 
164 一开始我是这样读的嘛
165 "我的回答" 10:27:52 
166 l2[x]是log2(x)
167 "我的回答" 10:28:02 
168 l2[8]就是3
169 "我的回答" 10:28:13 
170 然后我就把2 4 8 16变成了1 2 3 4
171 ///提问者 10:29:49 
172 在状态转移的过程中哪里知道是否有相同的存在
173 "我的回答" 10:30:16 
174                     while( (t & (1<<k)) ) {
175                         get+=p2[k+2];
176                         k++;
177                     }
178 "我的回答" 10:30:27 
179 t是状态
180 "我的回答" 10:31:09 
181 t & (1<<k)为真,说明t的第(k+1)位为真
182 "我的回答" 10:31:36 
183 就说明有第(k+1)位代表的数,就能知道能不能合了
View Code

 

 

还不懂可以用这个演示程序试一试,它会输出每次状态转移,和最后的状态是由哪些状态得来的:

  1 //#pragma comment(linker, "/STACK:102400000,102400000")
  2 #include<cstdio>
  3 #include<cmath>
  4 #include<iostream>
  5 #include<cstring>
  6 #include<algorithm>
  7 #include<cmath>
  8 #include<map>
  9 #include<set>
 10 #include<stack>
 11 #include<queue>
 12 using namespace std;
 13 #define ll long long
 14 #define usll unsigned ll
 15 #define mz(array) memset(array, 0, sizeof(array))
 16 #define mf1(array) memset(array, -1, sizeof(array))
 17 #define minf(array) memset(array, 0x3f, sizeof(array))
 18 #define REP(i,n) for(i=0;i<(n);i++)
 19 #define FOR(i,x,n) for(i=(x);i<=(n);i++)
 20 #define RD(x) scanf("%d",&x)
 21 #define RD2(x,y) scanf("%d%d",&x,&y)
 22 #define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
 23 #define WN(x) prllf("%d\n",x);
 24 #define RE  freopen("D.in","r",stdin)
 25 #define WE  freopen("1biao.out","w",stdout)
 26 #define mp make_pair
 27 #define pb push_back
 28 const int maxn=511;
 29 int p2[14];
 30 
 31 int n;
 32 int a[maxn];
 33 
 34 int f[maxn][4096];///f[i][j],j为状态,集合,j二进制第i位为1表示存在元素2^i。
 35 ///最多合出4096,是2^12,所以开12位来存,2^12=4096。
 36 int g[maxn][4096];
 37 int farm() {
 38     int i,j,k;
 39     mf1(f);
 40     f[0][0]=0;
 41     for(i=1; i<=n; i++) {
 42         for(j=0; j<4096; j++) {
 43             if(f[i-1][j]!=-1) {
 44                 if(f[i-1][j]>f[i][j]) { ///不选当前点
 45                     f[i][j]=f[i-1][j];
 46                     g[i][j]=j;
 47                 }
 48                 ///需要100000(1是第a[i]位)才能直接合
 49                 ///若  000000则变成100000
 50                 ///若  0?????(?中有1)则把?删了变成100000,
 51                 ///若  1????? 则需要把?删了变成100000,不能合成
 52                 ///综合起来就是比a[i]低的位都是0的话才能合,不然就不能合,除了a[i]全部位变成0,a[i]位变成1
 53                 int t=j;
 54                 k=a[i]-1;
 55                 int q=(1<<k) -1 ;///比k低的位数全是1
 56                 int get=p2[a[i]];
 57                 if((t&q)==0) {///比k低的位全是0
 58                     while( (t & (1<<k)) ) {
 59                         get+=p2[k+2];
 60                         k++;
 61                     }
 62                     q=(1<<k) -1;
 63                     t&=~q;///把比k低的位数全变成0,怕不怕
 64                     t|=1<<k;
 65                 }else t=1<<k;///只有k位是1
 66                 int newscore=f[i-1][j]+get;
 67                 if(newscore > f[i][t]) {
 68                     f[i][t]=newscore;
 69                     g[i][t]=j;
 70                     printf("i=%d, j=%d, k=%d, t=%d, get=%d, f[i-1][j]=%d, newscore=%d\n",i,j,k,t,get,f[i-1][j],newscore);
 71                 }
 72             }
 73         }
 74     }
 75     int ansj=0;
 76     for(j=1; j<4096; j++) {
 77         if(f[n][j]>f[n][ansj]) {
 78             ansj=j;
 79         }
 80     }
 81     k=ansj;
 82     for(i=n; i>=1; i--) {
 83         printf("i=%d j=%d(%x) f[i][j]=%d\n",i,k,k,f[i][k]);
 84         k=g[i][k];
 85     }
 86     return f[n][ansj];
 87 }
 88 
 89 int l2[17];
 90 int main() {
 91     int T,i,x;
 92     l2[2]=1;
 93     l2[4]=2;
 94     l2[8]=3;
 95     l2[16]=4;///l2[i]=log2(i)
 96     p2[0]=1;
 97     for(i=1; i<=13; i++)///最多就把500个16合在一起(其实不到),8000,2^13=8192
 98         p2[i]=p2[i-1]*2;///p2[i]=pow(2,i)
 99     scanf("%d",&T);
100     while(T--) {
101         scanf("%d",&n);
102         for(i=1; i<=n; i++) {
103             scanf("%d",&x);
104             a[i]=l2[x];
105         }
106         printf("%d\n",farm());
107     }
108     return 0;
109 }
View Code

 

转载于:https://www.cnblogs.com/yuiffy/p/3935760.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值