多米諾骨牌
有上下 個方塊組成,每個方塊中有 個點。現有排成行的 個 多米諾骨牌
。
上方塊中點數之和記爲 ,下方塊中點數之和記爲 ,它們的差爲 。例如在下圖中,。每個 多米諾骨牌
可以旋轉 ,使得上下兩個方塊互換位置。 編程用最少的旋轉次數使多米諾骨牌上下 行點數之差達到最小(即有 個要求:第一要求爲上下點數之差的絕對值最小,第二是在滿足第一的條件下,翻轉的次數最少)。
對於圖中的例子,只要將最後一個多米諾骨牌旋轉 ,可使上下 行點數之差爲 。
容易發現,dp
的狀態肯定要有一維來維護上下差的絕對值和翻轉次數,有幾種方法:
- 維護翻轉次數。但是對於這題而言,不能轉移。
- 直接維護差的絕對值。對於所有需要維護差的絕對值的題,它是通用的,但是因爲絕對值,轉移有點複雜(需要分類討論)。
- 因爲翻轉不會改變 的值,即總和不變。因爲我們已經知道了和,於是我們知道 中的一個後,就可以知道另一個,進而知道 。
對於本題而言,我們自然而然會選擇第 種方法。記 表示考慮到第 個 多米諾骨牌
, 時最小的轉移次數。
即當前的 多米諾骨牌
的上面的數字爲 ,下面的數爲 ,則有轉移:
上面的轉移表示當前的 多米諾骨牌
不翻轉,下面的表示翻轉。
初始化:所有的 無窮,。特別地,如果 ,。
時間複雜度和空間複雜度都是 。
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;
}
- 數組一定要開夠 ,因爲每個
多米諾骨牌
的數值最多爲 ,所以 最多爲 。 for(int j=0;j<=6*n;j++)
處,不能寫成for(int j=0;j<=6*i;j++)
,因爲下一個多米諾骨牌
的 值我們不知道,可能需要用到第二維的值會大於 ,像代碼那樣寫最保險。