题意:就像那个游戏2048一样。。不过是一维版的。有n个数,只有可能是2,4,8,16,两个相同的数合并可以double并得到 等于它们的和 的额外积分。问最多拿到多少分。
思路:状压dp。二进制表示集合,集合的内容是,当前栈中“可能会被”合并掉的数。首先有个很重要的结论,就是栈中如果有可以被合并的n,那么不会有多于1个n可以被合并。比如当前栈是 x 8 x x 8,假设这两个8都可以被合并,那么右边的8(靠近栈顶)会先被合并,得到16,这样的话,不加入新的数的情况下,这个栈就不会产生小于16的数了,也就是被16堵住了,前面小于16的数统统不能被合并。因此,可以用一个二进制数来表示这个栈中可能被合并的数,当然不能被合并的那些数我们不关心。
有了这样的想法,就可以用一个滚动数组来dp了,数组开8192,因为n最大是500,512*16=8192。要注意对滚动数组的更新。
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <cstdlib>
#include <string>
#include <memory.h>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <ctype.h>
#define INF 1000000
#define ll long long
#define min3(a,b,c) min(a,min(b,c))
using namespace std;
int num[512];
int dp[8192][2];
int main(){
int t;
cin>>t;
int n;
while(t--){
cin>>n;
int ans=0;
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>num[i];
}
dp[0][0]=dp[0][1]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<8192;j++){
if(dp[j][!(i&1)]==-1)continue;
//更新
dp[j][i&1]=max(dp[j][i&1],dp[j][!(i&1)]);
int cur=num[i];
//t描述了新栈的情况
int t;
if(j&(cur-1)){
t=cur;//堵住了比当前数小的数
}else{
t=j+cur;
}
//s是得到的额外积分
int s=0;
int p=j;
if( !(j&(cur-1)) )while(p&cur){
p=p-cur;
cur<<=1;
s+=cur;
}
//原积分+额外积分+当前拿到的数
if(dp[j][!(i&1)]+s+num[i]>dp[t][i&1]){
dp[t][i&1]=dp[j][!(i&1)]+s+num[i];
}
ans=max(dp[t][i&1],ans);
}
}
cout<<ans<<endl;
}
return 0;
}