【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$就好啦
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 }挫挫滴代碼
【noip模擬】2048