1. 程式人生 > 其它 >石子合併(弱化版)

石子合併(弱化版)

洛谷題面

題目大意

設有 \(N(N \le 300)\) 堆石子排成一排,其編號為 \(1,2,3,\cdots,N\)。每堆石子有一定的質量 \(m_i(m_i \le 1000)\)

現在要將這 \(N\) 堆石子合併成為一堆。每次只能合併相鄰的兩堆,合併的代價為這兩堆石子的質量之和,合併後與這兩堆石子相鄰的石子將和新堆相鄰。合併時由於選擇的順序不同,合併的總代價也不相同。

試找出一種合理的方法,使總的代價最小,並輸出最小代價。

題目分析

辨別區間 \(\rm DP\) 的方式:

從小區間延伸到大區間(合併等方式)。

許多括號數數題就是區間 \(\rm DP\) 好題,今年 \(\verb!2021-CSP-S-括號序列!\)

不妨一做。


\(dp[l][r]\) 表示區間 \([l,r]\) 內合併的最小代價。

列舉左右區間,再列舉中間斷點來判斷是否答案會變得更優:

\(dp[l][r]=\min\{dp[l][k]+dp[k+1][r]+cost\}(l\le k\lt r)\)

其中 \(cost\) 表示 \(\sum\limits_{i=l}^{r}a_i\)

列舉區間(左右+斷點)複雜度為 \(O(N^3)\),中間列舉價值的部分又是 \(O(N)\),總時間複雜度 \(O(N^4)\)

列舉價值部分可以通過字首和優化,優化為 \(O(N^3)\)

程式碼

//2022/1/6

const int INF=0x3f3f3f3f;

const int ma=305;

int a[ma],sum[ma];

int dp[ma][ma];

int n;

int main(void)
{
	n=read();
	
	Input_Int(n,a);
	
	for(register int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+a[i];
	}
	
	for(register int len=2;len<=n;len++)
	{
		for(register int l=1;l+len-1<=n;l++)
		{
			int r=l+len-1;
			
			dp[l][r]=INF;
			
			for(register int k=l;k<r;k++)
			{
				int w=sum[r]-sum[l-1];
				
				dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+w);
			}
		}
	}
	
	printf("%d\n",dp[1][n]);
	
	return 0;
}