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? InputThe 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. OutputFor each case, you should output an integer. The answer is the maximum of the total score which Dark_sun may get. Sample Input3 Sample Output34 HintIn 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])
加入当前元素:当递减序列里没有比当前元素小的元素,才可以合并;当递减序列里有比当前元素小的,就不能合并,而且新加入的元素作为新的递减序列(新的递减序列只包括这一个元素了)。
具体动规代码(滚动数组):
int now=,pre=;
mf1(f);
f[pre][]=;
for(i=; i<=n; i++) {
for(j=; j<; j++) {
if(f[pre][j] != -) {
f[now][j]=max(f[now][j],f[pre][j]);///不选当前点 int t=j;///状态
k=a[i]-;///a[i]代表的位的位置,第k位
int q=(<<k) - ;///q为( 比k低的位数全是1 )的数
int get=p2[a[i]];///新得分
if((t&q)==) {///如果比k低的位全是0,才能合并
while( (t & (<<k)) ) {///合并,数之前有多少个连续的1,统计合并得分
get+=p2[k+];
k++;
}
q=(<<k) -;
t&=~q;///把比k低的位数全变成0,怕不怕
t|=<<k;
}else t=<<k;///不能合并,产生新递减序列,只有k位是1
f[now][t]=max( f[now][t] , f[pre][j]+get );
}
}
swap(now,pre);
}
最后找f[n][j]的最大值就行了。
(怕了,比赛的时候直接看样例,没看题,以为是一维的2048,不会自动合并,我可以选后面的先合……逗乐!居然是Flappy2048,谁会玩那种逗游戏啊!看来以后还是要看题……然后我也没想到状压,其实还是挺简单的,只是我状压做得少/.\)
(这题不滚动数组,用[501][4096]也不会MLE,但是时间一秒六……大概数据组数多,清零耗时太多。滚动数组只要100ms左右)
全代码(滚动数组):
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
#define usll unsigned ll
#define mz(array) memset(array, 0, sizeof(array))
#define mf1(array) memset(array, -1, sizeof(array))
#define minf(array) memset(array, 0x3f, sizeof(array))
#define REP(i,n) for(i=0;i<(n);i++)
#define FOR(i,x,n) for(i=(x);i<=(n);i++)
#define RD(x) scanf("%d",&x)
#define RD2(x,y) scanf("%d%d",&x,&y)
#define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define WN(x) prllf("%d\n",x);
#define RE freopen("D.in","r",stdin)
#define WE freopen("1biao.out","w",stdout)
#define mp make_pair
#define pb push_back
const int maxn=;
int p2[]; int n;
int a[maxn]; int f[][];///f[i][j],j为状态,集合,j二进制第i位为1表示存在元素2^i。
///最多合出4096,是2^12,所以开12位来存,2^12=4096。
int farm() {
int i,j,k;
int now=,pre=;
mf1(f);
f[pre][]=;
for(i=; i<=n; i++) {
for(j=; j<; j++) {
if(f[pre][j] != -) {
f[now][j]=max(f[now][j],f[pre][j]);///不选当前点 int t=j;///状态
k=a[i]-;///a[i]代表的位的位置,第k位
int q=(<<k) - ;///q为( 比k低的位数全是1 )的数
int get=p2[a[i]];///新得分
if((t&q)==) {///如果比k低的位全是0,才能合并
while( (t & (<<k)) ) {///合并,数之前有多少个连续的1,统计合并得分
get+=p2[k+];
k++;
}
q=(<<k) -;
t&=~q;///把比k低的位数全变成0,怕不怕
t|=<<k;
}else t=<<k;///不能合并,产生新递减序列,只有k位是1
f[now][t]=max( f[now][t] , f[pre][j]+get );
}
}
swap(now,pre);
}
int re=;
for(j=; j<; j++) {
re=max(re,f[pre][j]);
}
return re;
} int l2[];
int main() {
int T,i,x;
l2[]=;
l2[]=;
l2[]=;
l2[]=;///l2[i]=log2(i)
p2[]=;
for(i=; i<=; i++)///最多就把500个16合在一起(其实不到),8000,2^13=8192
p2[i]=p2[i-]*;///p2[i]=pow(2,i)
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(i=; i<=n; i++) {
scanf("%d",&x);
a[i]=l2[x];
}
printf("%d\n",farm());
}
return ;
}
代码FAQ(回答别人问题时写的,可以帮助理解)
///提问者 9:58:39
你把思路说一遍吧
///提问者 9:59:27
合并不是要相等的才合并吗
"我的回答" ::
f[i][j],记第1到第i个元素中选若干个,达到状态j时的得分
"我的回答" ::
对啊
///提问者 10:00:02
状态j是什么状态啊
"我的回答" ::
状态是二进制各位表示是否有可以合并的2,是否有可以合并的4,是否有8……是否有可以合并的4096
///提问者 10:00:18
为什么递减就能合并,
"我的回答" ::
比如j=二进制的0000000111,就说明有2和4和8
"我的回答" ::
因为如果某次递增了,比如队列是8
"我的回答" ::
那之前的8 2就不可能再合并了
"我的回答" ::
可能合并的只有4,状态就是0000010
///提问者 10:02:45
没了啊
"我的回答" ::
双击查看原图有没有理解
///提问者 10:04:01
那么怎么表示有8 4的队列呢
///提问者 10:05:41
还有怎么表示它加还是不加元素呢
"我的回答" ::
这些在状态转移里
"我的回答" ::
世界上我们是每个状态都看一下
"我的回答" ::
初始的时候f[][]为0,其他都为-,表示没有那种状态。
"我的回答" ::
然后刚开始看8加入不加入,加入或者不加入我们都算出新的状态
"我的回答" ::
不加入的话,f[][]=f[][]=,得分还是0
"我的回答" ::
加入的话,递减序列集合里只有{},那状态就是00000100
"我的回答" ::
二进制的100 = 十进制的4
"我的回答" ::
f[][] = f[][]+ =
///提问者 10:08:50
能在麻烦一下,说一下案例8 2是怎么转移的吗
"我的回答" ::
我来用程序看一看
///提问者 10:14:12
得分是怎么加的。
"我的回答" :: i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i=, j=, k=, t=, get=, f[i-][j]=, newscore=
i= j=() f[i][j]=
i= j=(f) f[i][j]=
i= j=(e) f[i][j]=
i= j=(c) f[i][j]=
i= j=() f[i][j]=
i= j=() f[i][j]=
i= j=() f[i][j]=
i= j=() f[i][j]= 那个案例是由最后那8行所说的状态得来的,上面还算了每种状态
"我的回答" ::
得分是如果没合成就直接加,合成了的话就多加合成的分
"我的回答" :: int get=p2[a[i]];
if((t&q)==) {///比k低的位全是0
while( (t & (<<k)) ) {
get+=p2[k+];
k++;
}
q=(<<k) -;
t&=~q;///把比k低的位数全变成0,怕不怕
t|=<<k;
}else t=<<k;///只有k位是1
int newscore=f[i-][j]+get; 这个是算合成得分的
///提问者 10:18:09
那怎么知道是否合成,就算是递减也不能怎么合成
"我的回答" ::
p2[a[i]]是当前这个数的分
///提问者 10:18:23
get+=p2[k+];?
"我的回答" ::
这个p2[k+]是pow(,k+)
"我的回答" ::
就是合成这一次的得分
"我的回答" ::
是否合成可以看状态
"我的回答" ::
比如状态里得知递减序列里有8
///提问者 10:19:15
哪种状态可以合成
"我的回答" ::
那新加入的2就可以合成4,然后合成8,然后合成
"我的回答" ::
状态是8 ,新加入的是4的话,就不能合成
///提问者 10:20:53
这个p2[k+]是pow(,k+)这个不理解
"我的回答" ::
居然,这个是我预处理的
"我的回答" ::
先算好了2^k,存到p2[k]里
"我的回答" ::
然后我状态是每位存是否有某个数嘛,第一位为1说明有2,第二位为1说明有4
"我的回答" ::
然后如果新加入的数是8
"我的回答" ::
8是存在第三位的,我们要看第一位第二位是否是1,是1的话就说明有比8小的数
"我的回答" ::
就不能合成
"我的回答" ::
如果都为0,说明能合成,然后看第三位,为1的话就说明有8,能合成,合完了看第四位,就是看有没有16,这样一直合下去
///提问者 10:27:08
哪里体现了8是存在第三位的
"我的回答" ::
scanf("%d",&n);
for(i=; i<=n; i++) {
scanf("%d",&x);
a[i]=l2[x];
}
"我的回答" ::
一开始我是这样读的嘛
"我的回答" ::
l2[x]是log2(x)
"我的回答" ::
l2[]就是3
"我的回答" ::
然后我就把2 16变成了1
///提问者 10:29:49
在状态转移的过程中哪里知道是否有相同的存在
"我的回答" ::
while( (t & (<<k)) ) {
get+=p2[k+];
k++;
}
"我的回答" ::
t是状态
"我的回答" ::
t & (<<k)为真,说明t的第(k+)位为真
"我的回答" ::
就说明有第(k+)位代表的数,就能知道能不能合了
///提问者 10:27:08
哪里体现了8是存在第三位的
"我的回答" ::
scanf("%d",&n);
for(i=; i<=n; i++) {
scanf("%d",&x);
a[i]=l2[x];
}
"我的回答" ::
一开始我是这样读的嘛
"我的回答" ::
l2[x]是log2(x)
"我的回答" ::
l2[]就是3
"我的回答" ::
然后我就把2 16变成了1
///提问者 10:29:49
在状态转移的过程中哪里知道是否有相同的存在
"我的回答" ::
while( (t & (<<k)) ) {
get+=p2[k+];
k++;
}
"我的回答" ::
t是状态
"我的回答" ::
t & (<<k)为真,说明t的第(k+)位为真
"我的回答" ::
就说明有第(k+)位代表的数,就能知道能不能合了
还不懂可以用这个演示程序试一试,它会输出每次状态转移,和最后的状态是由哪些状态得来的:
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
#define usll unsigned ll
#define mz(array) memset(array, 0, sizeof(array))
#define mf1(array) memset(array, -1, sizeof(array))
#define minf(array) memset(array, 0x3f, sizeof(array))
#define REP(i,n) for(i=0;i<(n);i++)
#define FOR(i,x,n) for(i=(x);i<=(n);i++)
#define RD(x) scanf("%d",&x)
#define RD2(x,y) scanf("%d%d",&x,&y)
#define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define WN(x) prllf("%d\n",x);
#define RE freopen("D.in","r",stdin)
#define WE freopen("1biao.out","w",stdout)
#define mp make_pair
#define pb push_back
const int maxn=;
int p2[]; int n;
int a[maxn]; int f[maxn][];///f[i][j],j为状态,集合,j二进制第i位为1表示存在元素2^i。
///最多合出4096,是2^12,所以开12位来存,2^12=4096。
int g[maxn][];
int farm() {
int i,j,k;
mf1(f);
f[][]=;
for(i=; i<=n; i++) {
for(j=; j<; j++) {
if(f[i-][j]!=-) {
if(f[i-][j]>f[i][j]) { ///不选当前点
f[i][j]=f[i-][j];
g[i][j]=j;
}
///需要100000(1是第a[i]位)才能直接合
///若 000000则变成100000
///若 0?????(?中有1)则把?删了变成100000,
///若 1????? 则需要把?删了变成100000,不能合成
///综合起来就是比a[i]低的位都是0的话才能合,不然就不能合,除了a[i]全部位变成0,a[i]位变成1
int t=j;
k=a[i]-;
int q=(<<k) - ;///比k低的位数全是1
int get=p2[a[i]];
if((t&q)==) {///比k低的位全是0
while( (t & (<<k)) ) {
get+=p2[k+];
k++;
}
q=(<<k) -;
t&=~q;///把比k低的位数全变成0,怕不怕
t|=<<k;
}else t=<<k;///只有k位是1
int newscore=f[i-][j]+get;
if(newscore > f[i][t]) {
f[i][t]=newscore;
g[i][t]=j;
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-][j],newscore);
}
}
}
}
int ansj=;
for(j=; j<; j++) {
if(f[n][j]>f[n][ansj]) {
ansj=j;
}
}
k=ansj;
for(i=n; i>=; i--) {
printf("i=%d j=%d(%x) f[i][j]=%d\n",i,k,k,f[i][k]);
k=g[i][k];
}
return f[n][ansj];
} int l2[];
int main() {
int T,i,x;
l2[]=;
l2[]=;
l2[]=;
l2[]=;///l2[i]=log2(i)
p2[]=;
for(i=; i<=; i++)///最多就把500个16合在一起(其实不到),8000,2^13=8192
p2[i]=p2[i-]*;///p2[i]=pow(2,i)
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(i=; i<=n; i++) {
scanf("%d",&x);
a[i]=l2[x];
}
printf("%d\n",farm());
}
return ;
}
ZOJ3802 Easy 2048 Again (状压DP)的更多相关文章
- Codeforces Round #568 (Div. 2) G1. Playlist for Polycarp (easy version) (状压dp)
题目:http://codeforces.com/contest/1185/problem/G1 题意:给你n给选项,每个选项有个类型和价值,让你选择一个序列,价值和为m,要求连续的不能有两个相同的类 ...
- zoj3802:easy 2048 again(状压dp)
zoj月赛的题目,非常不错的一个状压dp.. 题目大意是一个一维的2048游戏 只要有相邻的相同就会合并,合并之后会有奖励分数,总共n个,每个都可以取或者不取 问最终得到的最大值 数据范围n<= ...
- 刷题向》关于第一篇状压DP BZOJ1087 (EASY+)
这是本蒟蒻做的第一篇状压DP,有纪念意义. 这道题题目对状压DP十分友善,算是一道模板题. 分析题目,我们发现可以用0和1代表每一个格子的国王情况, 题目所说国王不能相邻放置,那么首先对于每一行是否合 ...
- Codeforces 544E Remembering Strings 状压dp
题目链接 题意: 给定n个长度均为m的字符串 以下n行给出字符串 以下n*m的矩阵表示把相应的字母改动成其它字母的花费. 问: 对于一个字符串,若它是easy to remembering 当 它存在 ...
- 多米诺骨牌放置问题(状压DP)
例题: 最近小A遇到了一个很有趣的问题: 现在有一个\(n\times m\)规格的桌面,我们希望用\(1 \times 2\)规格的多米诺骨牌将其覆盖. 例如,对于一个\(10 \times 11\ ...
- hdu 3247 AC自动+状压dp+bfs处理
Resource Archiver Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 100000/100000 K (Java/Ot ...
- POJ 2411 Mondriaan's Dream -- 状压DP
题目:Mondriaan's Dream 链接:http://poj.org/problem?id=2411 题意:用 1*2 的瓷砖去填 n*m 的地板,问有多少种填法. 思路: 很久很久以前便做过 ...
- 状态压缩动态规划 状压DP
总述 状态压缩动态规划,就是我们俗称的状压DP,是利用计算机二进制的性质来描述状态的一种DP方式 很多棋盘问题都运用到了状压,同时,状压也很经常和BFS及DP连用,例题里会给出介绍 有了状态,DP就比 ...
- TZOJ 2289 Help Bob(状压DP)
描述 Bob loves Pizza but is always out of money. One day he reads in the newspapers that his favorite ...
随机推荐
- Java反射机制的作用
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类.那么第一个程序员的代码能否通过编译呢?这是不能通过编译的.利用Java反射的机制,就可以 ...
- 树莓派启用root账户
树莓派使用的linux是debian系统,所以树莓派启用root和debian是相同的. debian里root账户默认没有密码,但账户锁定. 当需要root权限时, 直接执行 sudo su 即可切 ...
- xudyh的gcd模板
hdu 5019 #include <cstdlib> #include <cctype> #include <cstring> #include <cstd ...
- Linux下修改进程名称
catalog . 应用场景 . 通过Linux prctl修改进程名 . 通过修改进程argv[]修改进程名 . 通过bash exec命令修改一个进程的cmdline信息 1. 应用场景 . 标识 ...
- Non Lasting Storage File System、procfs、sysfs
catalog . 引言 . proc文件系统 . 简单的文件系统 . sysfs 0. 引言 传统上,文件系统用于在块设备上持久存储数据,但也可以使用文件系统来组织.提供.交换并不存储在块设备上的信 ...
- Linux Device Driver && Device File
catalog . 设备驱动程序简介 . I/O体系结构 . 访问设备 . 与文件系统关联 . 字符设备操作 . 块设备操作 . 资源分配 . 总线系统 1. 设备驱动程序简介 设备驱动程序是内核的关 ...
- JSP文件下载
设置http响应头 response.setHeader 读取文件 这里需要注意两点: 读取的文件路径,必须设置为绝对路径 若文件名为中文,需要设置编码格式:URLEncoder.encode(fil ...
- Deep Learning in NLP (一)词向量和语言模型
原文转载:http://licstar.net/archives/328 Deep Learning 算法已经在图像和音频领域取得了惊人的成果,但是在 NLP 领域中尚未见到如此激动人心的结果.关于这 ...
- Mysql学习笔记(四)聊聊数据库索引
小心情(可直接跳到分割线后) 今天心情好些了.一些浓的化不开的坏情绪,也渐渐的在晚上解决掉一个复杂的逻辑问题后,渐渐消散了. 今天中午去吃饭的时候,坤哥漫不经心的说:'我这么多年终于悟出了一个道理,人 ...
- pc端,自适应屏幕分辨率
前端开发框架Bootstrap 网址:http://www.dnzs.com.cn/w3cschool/bootstrap/bootstrap-tutorial.html 需要加入代码 <sc ...