Online Judge:UVA12235c++
Label:思惟題,狀態定義,狀壓Dp數組
有一個書架,上面放了n本書,從左往右的第i本書的高度爲h[i]。定義書架的混亂度爲連續等高段的個數。優化
例如:{30,30,31,31,32}的混亂度爲3;{30,32,32,31}的混亂度爲3;{31,32,31,32,31}的混亂度爲5。spa
如今你能夠從中抽出至多k本書,而後將他們隨意放回書架任意位置,求最終最小的混亂度。code
對於100%的數據,N的範圍[1,500],K的範圍[1,100],書的高度範圍[25,32];get
第一行兩個整數N,K;it
第二行,N個元素,表示從左往右每本書的高度。class
輸出從新擺放K本書的最小混亂度。遍歷
"Print a blank line after the output of each case."
注意每組數據輸出要兩個換行!!二進制
Input
5 2 25 25 32 32 25 5 1 25 26 25 26 25 0 0
Output
Case 1: 2 Case 2: 3
發現書的高度只有\([25,32]\)這8種,彷佛能夠狀壓的樣子。對高度從新編號\(0\)~\(7\)方便後面表示,而且稱爲\(8\)種不一樣的顏色。
定義狀態\(dp[i][j][sta][co]\),只考慮前\(i\)本書,已經將其中\(j\)本書拿起來了(注意這裏是拿起來,實際上是採用一種上帝視角,先所有拿出來而後到時候一個一個再放回去),\(sta\)表示哪些顏色的書還沒拿起來過(用二進制表示狀態),\(co\)表示最近一次沒拿起來的書的顏色,Dp值表示此時(i及其以前)剩下來沒有移動的那些書的混亂度。
然而你會發現直接這樣定數組會MLE,如何優化呢,只要你把題解拉下來一點直接看轉移過程,就會發現,對於第一維來講每次只用上一層狀態,因此能夠滾動一下第一維。這樣空間就沒問題勒。
先來說講接下來具體如何實現:
初態:對於任意\(h∈[h[1],h[n]]\) 有 \(dp[i][i-1][1<<h][h]\)表示,除了當前這本書,前面的\(i-1\)本書都拿起來時的狀態;
終態:統計\(dp[n][j][sta][co]+val\)的最小值,其中\(val\)=\(all\) \(Xor\) \(sta\)中1的個數,其實就是那些拿起來的書的顏色種類總數。
而轉移呢能夠一邊讀入一邊進行。對於當前的高度\(h\)(先-24轉化成0~7中的數字),咱們枚舉\(i,sta,co\),詳見下面註釋:
inline void Do(int &x,int y){if(x==-1||x>y)x=y;} //枚舉第o本書 int g=0,all=0; Rep(o,1,n){ //讀入高度,預處理下 int h;scanf("%d",&h);h-=25; //滾動~ g^=1;memset(dp[g],-1,sizeof(dp[g])); //上面所說的初態初始化。注意要判:o-1<=m,由於第二維咱們只開到m的上限100,不特判可能會數組越界 if(o-1<=m)dp[g][o-1][1<<h][h]=1; //i:已拿個數 s:未選顏色狀態 c:最近一次沒動的書的顏色 Rep(i,0,min(o,m))Rep(s,1,all)Rep(c,0,7){//枚舉上一層狀態 if(dp[g^1][i][s][c]==-1)continue; //最近的位置上有一個跟h顏色相同的書沒拿走,很明顯把當前這個留下來較優 if(h==c)Do(dp[g][i][s][c],dp[g^1][i][s][c]); else{ //把當前這個留在原位,因爲前面一個跟本身顏色不一樣,因此混亂值+1 Do(dp[g][i][s|(1<<h)][h],dp[g^1][i][s][c]+1); //把當前這個拿起來 Do(dp[g][i+1][s][c],dp[g^1][i][s][c]); } } all|=1<<h;//all表示當前已經含有的顏色 }
最後的統計根據上面的終態
來就行了。
上完整代碼:
#include<bits/stdc++.h> #define Rep(a,b,c) for(int a=b;a<=c;++a) using namespace std; int n,m,dp[2][115][260][10]; inline void Do(int &x,int y){ if(x==-1||x>y)x=y; } int main(){ int cas=0; while(~scanf("%d%d",&n,&m)){ if(n==0||m==0)return 0;memset(dp,-1,sizeof(dp)); int g=0,all=0; Rep(o,1,n){ int h;scanf("%d",&h);h-=25; g^=1;memset(dp[g],-1,sizeof(dp[g])); if(o-1<=m)dp[g][o-1][1<<h][h]=1; Rep(i,0,min(o,m))Rep(s,1,all)Rep(c,0,7){ if(dp[g^1][i][s][c]==-1)continue; if(h==c)Do(dp[g][i][s][c],dp[g^1][i][s][c]); else{ Do(dp[g][i][s|(1<<h)][h],dp[g^1][i][s][c]+1); Do(dp[g][i+1][s][c],dp[g^1][i][s][c]); } } all|=1<<h; } int ans=n; Rep(i,0,m)Rep(s,1,all)Rep(c,0,7)if(~dp[g][i][s][c]){ int cnt=0,choose=all^s; Rep(o,0,7)if((1<<o)&choose)cnt++; ans=min(ans,dp[g][i][s][c]+cnt); } printf("Case %d: %d\n\n",++cas,ans); } }
從新看一遍這道題,難點主要在於正肯定義狀態。狀壓、滾動這兩個優化根據數據範圍和轉移過程並不難想。爲何能夠以這樣的順序從前到後遍歷轉移呢,考試的時候也有思考過這樣的順序,可是立刻能找到反例——可能前面的書也能夠放在他以後的塊裏。實際上仍是沒有想通那個上帝視角,即將這些拿出來的書先囤着,到時候一個一個貪心的插♂回去,不急着肯定出到底要放在哪一個塊裏。