1. 程式人生 > >經典DP 洛谷p1880 石子合並

經典DP 洛谷p1880 石子合並

什麽 amp 第一個 分享 成功 去哪裏 拆分 就會 sin

https://www.luogu.org/problemnew/show/P1880 題目

題目描述

在一個圓形操場的四周擺放N堆石子,現要將石子有次序地合並成一堆.規定每次只能選相鄰的2堆合並成新的一堆,並將新的一堆的石子數,記為該次合並的得分。

試設計出1個算法,計算出將N堆石子合並成1堆的最小得分和最大得分.

輸入輸出格式

輸入格式:

數據的第1行試正整數N,1≤N≤100,表示有N堆石子.第2行有N個數,分別表示每堆石子的個數.

輸出格式:

輸出共2行,第1行為最小得分,第2行為最大得分.

輸入輸出樣例

輸入樣例#1: 復制
4
4 5 9 4
輸出樣例#1: 復制
43
54


卡了我蠻久的。。各種弱智錯誤。分享經驗,順便記錄自己的錯誤。

首先,還是說思想:
貪心:每次都選取最小的兩個進行合並,這樣就能得到局部最優,也就是只看每次合並都算是最小的
DP:什麽是dp呢,在我看來就是通過一種數學統計的方法從小狀態轉移到大狀態(妙用max,min),事實上感覺像一個記憶化的枚舉思想(只是感覺像,幫助理解,不嫩用來定義逃)

說完了二者的區別,談談dp最重要的東西
狀態轉移方程
要找到狀態轉移方程,就要yy出 這個大答案,可以由什麽樣的小答案通過題意的操作得到
比如這道題,屬於區間dp
什麽意思呢
它的大答案,就是大區間的答案,可以由小區間的答案,通過合並加分的操作
進行統計 對應我給的那句話,應該不難理解叭

有了yy的方向,就要大膽寫方程,這道題的方程其實不難找:
			dpmax[i][j] = max(dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j] + sum(i, j));
			dpmin[i][j] = min(dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j] + sum(i, j));
進行解釋
區間i, j的最大(最小)值  == 區間i, k的得分的最大(最小)值 + 區間k + 1, j的得分的最大(最小)值 + 本次合並的得分(下面都用sum)
到這裏沒人不理解叭

好了,方程出來了,想辦法實現,要先搞清楚這循環怎麽寫

dp[i,j] 很簡單,就直接兩個for從1到n,從j到n(雖然下面並不是這樣寫的,不過先別急,這是可以算是一個dp入門教程,但不是入門題啊)
於是就有
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
dp[i][j] = max(dp[i][j], ??????);

  ?怎麽辦,k去哪裏了

別急,先想想k是什麽,要怎麽開始,到哪裏結束

emmmm 直接說吧,k,應該是在dp[i][j] 時 從i到j的一個我認為可以叫枚舉的(極其不負責)東西叭,反正就是每個區間都要用for進行嘗試


於是

for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
for (int k = i; k <= j; k++)
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + sum(i, j));

  

但是這裏我們會發現一個問題

eg: dp[1][3] = max(dp[1][3], dp[1][2] + dp[2][3] + sum(1, 3)); 當進行到這裏時,會發現dp[2][3]好像從來都還沒算過,難道後面會算嗎。答案是會算,但是,dp[1][[3]已經不會再更新了,所以這樣統計答案肯定會出問題

那麽是不是方程出了問題,沒有啊,很完美啊!!!

確實,非常完美(畢竟是題解)

那麽這個問題怎麽解決呢?

回到一開始的思想,我們要先算完所有小區間的答案,再去統計大區間的答案對不對?那麽是不是從長度為1的區間開始運算?如果到這裏都能夠理解的話,這題就基本上成功了,但是AC嘛,你懂的


好,那麽重新開始

第一層循環用長度,從1開始

然後第二層用要統計的區間的起點,那是不是就不需要終點了,因為終點就等於長度加起點

第三層循環統計i到j區間內拆分的每一種情況

如下:

for (int len = 1; len < n; len++)
for (int i = 1; i <= n; i++)
{
	int j = i + len;
	for (int k = i; k <= j; k++)
	dp[i][j] = max(dp[i][j], dp[i][k]+ dp[k + 1][j] + sum(i, j);
} 

  

非常接近答案了,各位加油

現在大家可以去翻一下最後ac代碼,發現dp這個主要的循環基本上一致

然後,回到題面,發現這是個環形的石堆???瞬間腦子裏蹦出指針啊,int p啊什麽的,但是一激動,一寫,啊,好難啊好煩啊

這裏就有一個對付環經常用到的思想,把數組復制一遍,擴展成兩倍

例如:

A B C D是題目給的堆

復制一遍 A B C D A B C D

如果用這個復制後的數組去dp,會發生什麽,你是不是就統計到了D 到 A 或者D 到 B 或者D 到 C 的答案.....

為什麽這樣是正確的呢,大部分人應該都理解,但是還是想說一下

我們這樣想,如果它是環的話,是不是A 與 D就能合並(就是相鄰的),是不是就會統計出一個答案,我們需要的其實就是這個答案的數值,不用管什麽亂七八糟的想法,要數值大小就ok(有人就會有奇奇怪怪的想法

這個理解了,就去實現,直接看下面的最終代碼叭,一看就懂,只要不走神

那麽問題又來了,最後答案輸出什麽呢??dp[1][n] ??

肯定不對,那復制和沒復制有什麽區別。。

所以就要checkans檢查答案。。。),怎麽檢查?其實很簡單,只要把每個長度為len(即n - 1)的dp[i][j]都檢查一下,找出最大或者最小的那個,就是答案了

為什麽這樣又是對的??只因為檢查的長的為len

所有的問題都解決了,然後簡單優化(什麽平行四邊形優化都是dalao才會的東西。。)一下叭。

統計sum的優化--前綴和,在dp輸入時統計一下前i個石子的和,然後用的時候用前j個的和減掉前i個的和,就是sum(i, j)了

再考慮一下這個問題,復制數組的時候最後一位的復制好像並沒有用到?

eg:

A B C D A B C D

統計第一個D到第二個D的答案?不需要啊,長度超了,第一層循環限制了長度,根本訪問不到對吧

統計第二個A到第二個D的答案?也不要啊,這跟前面的第一個A到第二個D重復了,其他的其實也有重復,但是,我們需要D到AorBorC,所以其他的不需要刪

最後得出,最後一位其實不用復制,沒用,浪費(其實這個不算什麽優化,只是分享一下某位dalao的一個想法,覺得很有道理)

最後大家完成代碼是註意細節,什麽max,min的初始化啊,循環的邊界啊(我的代碼的邊界應該不完全恰當懶得檢查了,但是不會影響答案,得益於第一層循環的長度限制)

上代碼,祝大家acacacacacacac




#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 207;
int n, a[N], s[N], dpmin[N][N], dpmax[N][N], ansmax = -1, ansmin = 9999999;
inline int sum(int x, int y)
{
	return s[y] - s[x - 1];
}
int main(){
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) a[i + n] = a[i];
	for (int i = 1; i <= n * 2; i++) s[i] = s[i - 1] + a[i]; 
	/*
	for (int i = 1; i <= n * 2; i++)
		dpmax[i][i] = dpmin[i][i] = 0;
	for (int i = 1; i <= n * 2; i++)
		dpmax[i][i] = dpmin[i][i] = a[i] + a[i + 1];
	*/
	for (int len = 1; len < n; len++)
	for (int i = 1; i + len <= 2 * n; i++)
	{
		int j = i + len;
		dpmin[i][j] = 99999999;
		for (int k = i; k < j; k++)
		{
			dpmax[i][j] = max(dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j] + sum(i, j));
			dpmin[i][j] = min(dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j] + sum(i, j));
		}
	}
	for (int i = 1; i <= n + 1; i++) ansmax = max(ansmax, dpmax[i][i + n - 1]);
	for (int i = 1; i <= n + 1; i++) ansmin = min(ansmin, dpmin[i][i + n - 1]);
	printf("%d\n%d", ansmin, ansmax);
	return 0;
}

  

感謝閱讀

經典DP 洛谷p1880 石子合並