【noip模拟】2048
Time limit: 1000ms Memory limits: 256MB
Description
2048曾经是一款风靡全球的小游戏。
今天,我们换一种方式来玩这个小游戏。
现在,你有一个双端队列,你只能把元素从左端或从右端放入双端队列中。一旦放入就不得取出。放入后,若队列中有连续两个相同的元素,它们将自动合并变成一个新的元素——原来那两个元素的和。若新的元素与它相邻的元素相同,则继续合并……
如:双端队列中有2, 4, 16三个元素。若将2从左端插入双端队列中,该队列将变成8, 16。若将2从右端插入双端队列中,该队列将变成2, 4, 16, 2。
一开始,双端队列为空。我们将给你一些数,你需要依次插入到双端队列中。问是否存在一种操作方案,使得双端队列最后只剩下一个数。
Input
第一行为一个整数 $T$ ,表示数据组数。
对于每组测试数据:
第一行一个整数 $n$ ,表示我们给你的数的个数。
接下来一行 $n$ 个数,表示我们给你的数 $a_i$ 。这些数都是2的非负整数次方,即对于每个 $i$ 都存在一个整数 $k$ 满足$ k≥0$ 且$ a_i=2^k $。
Output
对于每组数据,若不存在一种操作方案使得双端队列最后只剩一个数,则输出no。否则,输出一个长度为n的字符串,其中若第i个数从左边插入到双端队列,则第i个字符为$ l$ ;若第$i$个数从右边插入到双端队列,则第$i$个字符为$ r$ 。
Sample Input
3
9
2 8 4 1 1 4 4 4 4
5
2 16 4 8 2
3
2 2 2
Sample Output
rrrlllrrr
no
no
HINT
样例1解释
1、从右边插入2,双端队列变为2
2、从右边插入8,双端队列变为2 8
3、从右边插入4,双端队列变为2 8 4
4、从左边插入1,双端队列变为1 2 8 4
5、从左边插入1,双端队列变为4 8 4
6、从左边插入4,双端队列变为16 4
7、从右边插入4,双端队列变为16 8
8、从右边插入4,双端队列变为16 8 4
9、从右边插入4,双端队列变为32
数据范围与约定
对于20%的数据,$ n≤19,T≤100$
对于所有数据, $1≤n≤1000$, $\sum\limits_{i=1}^{n} a_i <=2^{13}$,其中$ n>20$的数据不超过150组。
[吐槽]
玄妙的一题!
嗯场上天真地认为自己搞出来了结果。。对拍停了很棒棒qwq
[题解]
看完正解之后感觉整个人都飞起来了qwq
考虑怎样才能消完
嗯这是很自然的想法吧当然是先看最后排出来怎样的序列才能消完啦
然后就会发现一个只有最后得到的是这样的先递增再递减的序列才可能消完,也就是:
一个序列
$l_1,l_2,l_3,l_4,...,l_{k0}, r_1,r_2,r_3,...,r_{k1} $
$(l_1<=l_2<=l_3<=...<=l_{k0}>=r_1>=r_2>=r_3>=...>=r_{k1})$
接着还会发现一个有趣的事情
如果说我知道了$LeftPart$(也就是递增部分),$RightPart$(递减部分)自然也就确定了
然后还会发现题目中的条件:$\sum\limits_{i=1}^{n} a_i <=2^{13}$
也就是说,如果枚举$l$的状态数至多只会有$2^{13}$
很友善啊
好的于是就可以考虑dp啦
考虑状压dp
由于知道了$LeftPart$, $RightPart$也确定了,所以我们的状态显然就只用记录$l$部分就好
定义$f_{i,j}$表示处理到第$i$个数,$l$部分的和为$j$这一状态是由哪个状态转移过来的
之所以要用这么奇怪的记录方式是因为。。要输出方案
此外再定义$g_{i,j}$表示处理到第$i$个数,$l$部分的和为$j$这一状态,第$i$个数从哪边放
显然这么定义完了之后输出就是反过来找一遍就好啦
那么接下来就是转移了
(二进制是好东西呀lowbit和highbit这种东西十分的吼啊)
首先看一下那个合并方式
会发现其实如果以二进制的方式来看的话,那些奇奇怪怪的合并啊之类的东西已经自动帮你处理好了
所以判断起来会很方便
对于第$i$个数
我们看它可以放在左边还是右边,可以放过去的条件就是$a_i<=$最左边/最右边那个数
判断的话,因为我们要求每一步操作之后都能让序列保持上面提到的合法性质
所以可以保证到最左边/最右边的数值是最小的
这样一来就可以直接用lowbit(也就是转为二进制后最靠右边的1代表的数值)来取得最边上的数的值了
(以下步骤的前提都是$f_{i-1,j}$有解)
如果往右边放的话就有
$f_{i,j}=j$ $g_{i,j}=r$
如果往左边放的话就有
$f_{i,j+a_i}=j$ $g_{i,j}=l$
但是同时要处理一下$j+a_i>=highbit(RightPart)$(也就是递减部分的最大的那个数值)的情况
为啥?因为我们的$f_{i,j}$中的$j$记录的是$LeftPart$(递增部分)的和
而当出现上述情况的时候,说明此时$LeftPart$这边所有的数已经合成了一个比$RightPart$大或者相等的数
这时我们可以将$LeftPart$这边的这个大数直接归为$RightPart$,然后$LeftPart$的和清零
好处?这样最后查询ans的时候,直接看$f_{n,0}$就可以啦
然后就很愉快滴做完啦ovo
噢终极无敌玄妙
[一些细节]
嗯因为我们最后查ans的时候,只有在有解的时候才是正确的(否则无法保证最后合成的是一个数)
所以对于那种本身和都不是一个2的若干次方的输入,直接输no
判断这个的话直接看$lowbit(x)$是否等于$x$就好啦
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int MAXN=1010; 6 const int MAXM=(1<<13)+10; 7 int highbit[MAXM],sum[MAXN]; 8 int a[MAXN],f[MAXN][MAXM]; 9 char rec[MAXN][MAXM]; 10 int n,T,ans; 11 int get_h(); 12 int print(int x,int y); 13 int lowbit(int x) {return x&-x;} 14 15 int main() 16 { 17 // freopen("a.in","r",stdin); 18 19 scanf("%d",&T); 20 get_h(); 21 int tmp; 22 for (int o=1;o<=T;++o) 23 { 24 scanf("%d",&n); 25 sum[0]=0; 26 for (int i=1;i<=n;++i) scanf("%d",a+i),sum[i]=sum[i-1]+a[i]; 27 if (sum[n]!=lowbit(sum[n])) {printf("no\n"); continue;} 28 for (int i=1;i<=n;++i) 29 { 30 for (int j=0;j<=sum[i];++j) 31 f[i][j]=-1; 32 for (int j=0;j<=sum[i-1];++j) 33 { 34 if (f[i-1][j]==-1) continue; 35 if (lowbit(j)>=a[i]||j==0) 36 { 37 if (j+a[i]>=highbit[sum[i-1]-j]) tmp=0; 38 else tmp=j+a[i]; 39 f[i][tmp]=j; 40 rec[i][tmp]='l'; 41 } 42 if (lowbit(sum[i-1]-j)>=a[i]||sum[i-1]-j==0) 43 { 44 f[i][j]=j; 45 rec[i][j]='r'; 46 } 47 } 48 } 49 if (f[n][0]==-1) printf("no"); 50 else print(n,0); 51 printf("\n"); 52 } 53 } 54 55 int get_h() 56 { 57 highbit[0]=1;highbit[1]=1; 58 for (int i=2;i<=1<<13;++i) 59 highbit[i]=highbit[i>>1]<<1; 60 } 61 62 int print(int x,int y) 63 { 64 if (x<1) return 0; 65 print(x-1,f[x][y]); 66 printf("%c",rec[x][y]); 67 }