2020.04.18日常總結——dp狀態的設計

洛谷P1282     多米諾骨牌\color{green}{\text{洛谷P1282\ \ \ \ \ 多米諾骨牌}}

【題意】:\color{blue}{\text{【題意】:}}

多米諾骨牌 有上下 22 個方塊組成,每個方塊中有 161-6 個點。現有排成行的 nn多米諾骨牌

上方塊中點數之和記爲 S1S_1,下方塊中點數之和記爲 S2S_2,它們的差爲 S1S2|S_1-S_2|。例如在下圖中,S1=6+1+1+1=9S2=1+5+3+2=11S1S2=2S_1=6+1+1+1=9,S_2=1+5+3+2=11,|S_1-S_2|=2。每個 多米諾骨牌 可以旋轉 180°180°,使得上下兩個方塊互換位置。 編程用最少的旋轉次數使多米諾骨牌上下 22 行點數之差達到最小(即有 22 個要求:第一要求爲上下點數之差的絕對值最小,第二是在滿足第一的條件下,翻轉的次數最少)。

在這裏插入圖片描述

對於圖中的例子,只要將最後一個多米諾骨牌旋轉 180°180°,可使上下 22 行點數之差爲 00

【思路】:\color{blue}{\text{【思路】:}}

容易發現,dp 的狀態肯定要有一維來維護上下差的絕對值和翻轉次數,有幾種方法:

  • 維護翻轉次數。但是對於這題而言,不能轉移。
  • 直接維護差的絕對值。對於所有需要維護差的絕對值的題,它是通用的,但是因爲絕對值,轉移有點複雜(需要分類討論)。
  • 因爲翻轉不會改變 S1+S2S_1+S_2 的值,即總和不變。因爲我們已經知道了和,於是我們知道 S1,S2S_1,S_2 中的一個後,就可以知道另一個,進而知道 S1S2|S_1-S_2|

對於本題而言,我們自然而然會選擇第 33 種方法。記 fi1,jf_{i-1,j} 表示考慮到第 ii多米諾骨牌S1=jS_1=j 時最小的轉移次數。

即當前的 多米諾骨牌 的上面的數字爲 aa,下面的數爲 bb,則有轉移:

fi,j={fi1,jajafi1,jb+1jb}f_{i,j}=\left \{ \begin{matrix} &f_{i-1,j-a} &j \geq a \\ &f_{i-1,j-b}+1 &j \geq b\end{matrix} \right \}

上面的轉移表示當前的 多米諾骨牌 不翻轉,下面的表示翻轉。

初始化:所有的 fi,j=f_{i,j}= 無窮,f1,b1=1,f1,a1=0f_{1,b_1}=1,f_{1,a_1}=0。特別地,如果 a1=b1a_1=b_1f1,a1=0f_{1,a_1}=0

時間複雜度和空間複雜度都是 O(n2)O(n^2)

【代碼】:\color{blue}{\text{【代碼】:}}

【AC代碼】:\color{red}{\text{【AC代碼】:}}

const int inf=0x3f3f3f3f;
int ansS,a[1100],b[1100],n;
int f[1050][6100],sum,ansT;
int main(){
	scanf("%d",&n);sum=0;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&a[i],&b[i]);
		sum+=a[i]+b[i];//累計和 
	}
	for(int i=1;i<=n;i++)
		for(int j=0;j<=6*n;j++)
			f[i][j]=inf;//初始化 
	f[1][b[1]]=1;f[1][a[1]]=0;
//	以上兩句初始化需要注意順序 
	for(int i=2;i<=n;i++)
		for(int j=0;j<=6*n;j++){
			if (j>=a[i]) f[i][j]=min(f[i][j],f[i-1][j-a[i]]);
			if (j>=b[i]) f[i][j]=min(f[i][j],f[i-1][j-b[i]]+1);
		}
	ansS=ansT=inf;//儲存答案 
	for(int i=0;i<=sum;i++)
		if (f[n][i]!=inf){//有解 
			if (abs(i-(sum-i))<=ansS){
				ansS=abs(i-(sum-i));
				ansT=f[n][i];
			}
			else if (abs(i-(sum-i))==ansS)
				ansT=min(ansT,f[n][i]);
		}
	printf("%d",ansT);
	return 0;
}

【注意】:\color{red}{\text{【注意】:}}

  • 數組一定要開夠 6×n6 \times n,因爲每個 多米諾骨牌 的數值最多爲 66,所以 S1S_1 最多爲 6×n6\times n
  • for(int j=0;j<=6*n;j++) 處,不能寫成 for(int j=0;j<=6*i;j++) ,因爲下一個 多米諾骨牌a,ba,b 值我們不知道,可能需要用到第二維的值會大於 6×i6 \times i,像代碼那樣寫最保險。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章