區間dp 合併石子問題

lulaalu發表於2024-04-12

合併石子問題

https://www.luogu.com.cn/problem/P1880

[NOI1995] 石子合併
題目大致描述

$N$堆石子擺成了一個圓,每相鄰的兩堆合成一堆,新的一堆的石子數為得分,求得分最小和最多

$1\leq N\leq 100$,$0\leq a_i\leq 20$。

解決思路:取每個區間的最大值

1、獲取資料,取得字首和

2、第一層for迴圈,用長度,從2到n

3、第二層for迴圈,用起點

4、在第三層for迴圈前先確定終點,然後第三層迴圈把每一個小區間列舉對比

關鍵程式碼實現:

//第一層 長度 
for(int len=2;len<=n;len++){
    //第二層 起始點 
    for(int i=1;i<=n-len+1;i++){
        //終點 
        int j=i+len-1;
        //由於是最小值,先賦初值 
        dpmin[i][j] = 1e9;
        //第三層  中間每一個最值
        for(int k=i;k<j;k++){
            //狀態轉移 本身與內部,但內部在合成後 還需要加上本次消耗的體力 
            dpmax[i][j]=max(dpmax[i][j],dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1]);
            dpmin[i][j]=min(dpmin[i][j],dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1]);
        }
    }
}

但是由於是圓形,需要考慮越過陣列終點與陣列開頭合併,所以用兩倍陣列,可以包含所有情況:

#include<iostream>
using namespace std;
const int N = 210;
long dpmin[N][N],dpmax[N][N],sum[N];
int n;

int main()
{
	cin >> n;
	sum[0] = 0;

	for (int i = 1; i <= n; i++){
		cin >> sum[i];
		sum[i + n] = sum[i];
		sum[i] += sum[i - 1];
	}
	for (int i = n + 1; i <= 2 * n; i++)
        sum[i] += sum[i - 1];
	
	long mins = 1e9,maxs = 0;

	for (int len = 2; len <= n ; len++){
		for (int i = 1; i <= 2 * n - len + 1; i++){
			int j = i + len - 1;
			dpmin[i][j] = 1e9;
			for (int k = i; k < j; k++){
				dpmin[i][j] = min(dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j] + sum[j] - sum[i - 1]);
				dpmax[i][j] = max(dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j] + sum[j] - sum[i - 1]);
			}
			if (len == k){
				mins = min(mins, dpmin[i][j]);
				maxs = max(maxs, dpmax[i][j]);
			}
		}
	}
	cout << mins << endl;
	cout << maxs << endl;

	return 0;
}

相關文章