[UVA12235] Help Bubu 思惟題+狀態定義+Dp

Online JudgeUVA12235c++

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);
	}
}

End

從新看一遍這道題,難點主要在於正肯定義狀態狀壓、滾動這兩個優化根據數據範圍和轉移過程並不難想。爲何能夠以這樣的順序從前到後遍歷轉移呢,考試的時候也有思考過這樣的順序,可是立刻能找到反例——可能前面的書也能夠放在他以後的塊裏。實際上仍是沒有想通那個上帝視角,即將這些拿出來的書先囤着,到時候一個一個貪心的插♂回去,不急着肯定出到底要放在哪一個塊裏。

相關文章
相關標籤/搜索