题意
2048曾经是一款风靡全球的小游戏。
今天,我们换一种方式来玩这个小游戏。
你有一个双端队列,你只能把元素从左端或从右端放入双端队列中。一旦放入就不得取出。放入后,若队列中有连续两个相同的元素,它们将自动合并变成一个新的元素——原来那两个元素的和。若新的元素与它相邻的元素相同,则继续合并……
如:双端队列中有2, 4, 16三个元素。若将2从左端插入双端队列中,该队列将变成8, 16。若将2从右端插入双端队列中,该队列将变成2, 4, 16, 2。
一开始,双端队列为空。我们将给你一些数,你需要依次插入到双端队列中。问是否存在一种操作方案,使得双端队列最后只剩下一个数。
(1le nle 1000,space sumlimits_{i=1}^{n}a_ile 2^{13},space Tle 10000),其中 (ngt 20) 的数据不超过 (150) 组。
题解
小学生手玩 (1s) 可得:如果一个数被夹在两个大于它的数中间,最后队列里就至少剩下 (3) 个数。
也就是说,任何时刻队列一定是单峰的,峰左边的数单调递增,峰右边的数单调递减。
所以直接贪心,判断新加入的数是否 (le) 队列左端的数,是则把新数加到队列左端;否则判断新加入的数是否 (le) 队列右端的数,是则把新数加到队列右端;否则该局面没救了,回溯改之前的某些两可情况(即之前加入某个数时,这个数同时 (le) 队列两端的数,可以加到任意一端。你可能只尝试加到了一端,现在回去改加到另一端)。
观察 (a_i),发现不仅都是 (2^k),而且总和 (le 2^{13}),那是不是随便二进制状压一下两边的数,然后记忆化搜索一下就行了?
状压显然可行,因为每个数在每一边的出现次数都是 (0) 或 (1),如果出现了 (2) 次,由于数列单调,这两个数相邻,所以会拼成一个更大的数。而每个数直接对应一个二进制位,拼两个数根本不用任何特殊操作,直接加上新来的数就自动进位了。
然而因为有 (1w) 组数据,复杂度貌似不太支持把两边都状压。
考虑可不可以只状压左边,把右边用左边的状态表示出来。不难发现由于是依次加入数,我们每次加入一个数后都知道所有加入的数之和,用总和减去左边的数之和 就是右边的数之和了。
#include<bits/stdc++.h>
#define N 1010
#define M 8195
#define R sum[x]-L
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
if(f) return x; return 0-x;
}
int t,T,n,highbit[M],a[N],sum[N],vis[N][M]; char ans[N]; bool flag;
inline int lowbit(int x){return x&-x;}
void dfs(int x, int L){
if(highbit[R] >= highbit[L]) L+=highbit[R];
if(vis[x][L]==T) return;
vis[x][L]=T;
if(x==n){
if(L==sum[x] && L==lowbit(L)) flag=1; //两条判断是等价的,可以只取其一
return;
}
int y=x+1, l=lowbit(L), r=lowbit(R);
if(a[y]<=l) ans[y]='l', dfs(y,L+a[y]);
if(flag) return;
if(!r || a[y]<=r) ans[y]='r', dfs(y,L);
}
int main(){
t=read();
for(int i=2; i<M; ++i) highbit[i]=highbit[i>>1]+1;
for(int i=1; i<M; ++i) highbit[i]=1<<highbit[i];
for(T=1; T<=t; ++T){
n=read();
for(int i=1; i<=n; ++i) a[i]=read(), sum[i]=sum[i-1]+a[i];
flag=0;
ans[1]='l', dfs(1,a[1]);
if(!flag) printf("no
");
else{ans[n+1]=0; printf("%s
",ans+1);}
}
return 0;
}