CSU 1592 石子归并(记忆化搜索 or 区间DP)
记忆化搜索
这是一道区间DP的模板题。初学dp的话还是记忆化搜索好理解一些,很多dp其实也是从记忆化搜索转换过来的。考虑最后一步,我们要把两堆石子合并成一堆,那么肯定是某一个点为分割点分成的两堆石子,而这两堆石子也是由某一个点为分割点组成的,如此递推下去。所以我们倒着把一堆石子拆成两堆,每次枚举分割点,最后回溯的时候取最小值就行了。
代码
const int maxn = 1e2+10;
int pre[maxn], rec[maxn][maxn];
int dfs(int l, int r) {
if (l==r) return 0;
cout << l << ' ' << r << endl;
if (rec[l][r]!=INF) return rec[l][r];
int res = INF, cost = pre[r]-pre[l-1];
for (int i = l; i+1<=r; ++i)
res = min(res, dfs(l, i)+dfs(i+1, r)+cost);
return rec[l][r] = res;
}
int main() {
IOS; int t; cin >> t;
while(t--) {
int n; cin >> n; INF(rec); zero(pre);
for (int i = 1; i<=n; ++i) {
cin >> pre[i];
pre[i] += pre[i-1];
}
cout << dfs(1,n) << endl;
}
return 0;
}
dp
我们看看之前的记忆化搜索不难发现,其最终是递归到最小的区间(大小为2)然后开始回溯的,所以我们如果要dp的话,就应该从最小的区间开始枚举,然后逐步往大的区间扩展。
代码
const int maxn = 1e2+10;
int pre[maxn], dp[maxn][maxn];
int main() {
int t; cin >> t;
while(t--) {
int n; cin >> n; zero(pre); zero(dp);
for (int i = 1; i<=n; ++i) {
cin >> pre[i];
pre[i] += pre[i-1];
}
for (int len = 1; len<n; ++len)
for (int l = 1; l+len<=n; ++l) {
int r = l+len;
dp[l][r] = INF;
for (int k = l; k+1<=r; ++k)
dp[l][r] = min(dp[l][r], dp[l][k]+dp[k+1][r]+pre[r]-pre[l-1]);
}
cout << dp[1][n] << endl;
}
return 0;
}