第八周动态规划区间DP
乘法游戏
题解:
桌子上总会有最左边和最右边的牌,这两张牌在进行过程中,不能被拿走,直到最后剩下这两张牌。
得分的规则是用你拿起的牌乘左侧牌的数再乘右侧牌的数。求得分最小。每次都有一张牌被拿走,与得分有关系的数分别是被拿走的牌,以及它左右两侧的牌,还有最左最右的牌。我们要找出最后被拿走的牌,设这张牌为k,那么就分成了两个区间,最左侧到k和k到最右侧。在小区间中继续分,枚举出所有情况,找出最优解。
d[i][j]表示第i个数到第j个数合并的最小值状态转移方程:
d[i][j]=min(d[i][j],d[i][k]+d[k][j]+a[i]*a[j]*a[k])i<k<j
代码:
#include <stdio.h>
#include<iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int M=105;
int main()
{
int n,a[M],dp[M][M];
while(~scanf("%d",&n))
{
for(int i = 1; i<=n; i++)
scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
for(int l = 2; l<n; l++)
{
for(int i = 2; i+l-1<=n; i++)
{
int j;
j = i+l-1;
dp[i][j] = 100000000;
for(int k = i; k<j; k++)
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+a[i-1]*a[k]*a[j]);
}
}
printf("%d\n",dp[2][n]);
}
return 0;
}
完整括号问题
这个题其实最主要的还是注意一下刚开始的初始化,这个题目的难点应该就是在这个上面了。
区间dp,dp[i][j]表示[i,j]子串中最长的合法括号子序列是多长。按照状态转移方程,求出最终解。
状态转移方程:
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))
{
dp[i][j]=max(dp[i][j],dp[i+1][j-1]+2);
}
总代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int M=105;
int dp[M][M];
char ch[M];
int i,j,k,t,v;
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
while(gets(ch+1))
{
if(ch[1]=='e') break;
memset(dp,0,sizeof dp);
int l=strlen(ch+1);
for(int t=1; t<=l; t++)
{
for(int i=1,j=t; j<=l; i++,j++)
{
if((ch[i]=='('&&ch[j]==')')||(ch[i]=='['&&ch[j]==']'))
dp[i][j]=dp[i+1][j-1]+2;
for(int k=i; k<j; k++)
{
if(dp[i][j]<dp[i][k]+dp[k+1][j])
dp[i][j]=dp[i][k]+dp[k+1][j];
}
}
}
cout<<dp[1][l]<<endl;
}
return 0;
}
愤怒值(小黑屋)
当n=2时:只要考虑两个人谁先谁后,比较两个人的愤怒值,排序即可。
当n>2时,队伍人数扩大后,分成小区间。每个小区间的愤怒值排序要排好。考虑队伍中的第一个人,安排在第几位,设dp[i][j]为第i个人到第j个人能达到的最小总愤怒值。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int M= 105;
const int inf = 0x3f3f3f3f;
int t, n, ans;
int aa[M], bb[M];
int dp[M][M];
int main()
{
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", bb + i);
bb[i] = bb[i - 1] + aa[i];
}
for (int p = 1; p< n; p++)
{
for (int l = 1; l + p <= n; l++)
{
int r = l + p;
dp[l][r] = inf;
for (int k = l; k <= r; k++)
dp[l][r] = min(dp[l][r], aa[l] * (k - l) + dp[l + 1][k] + dp[k + 1][r] + (bb[r] - bb[k]) * (k - l + 1));
}
}
printf("Case #%d: %d\n", ++ans, dp[1][n]);
}
return 0;
}
宴会服装
对于区间(i,j)如果第i件衣服和区间内的其他从i+1到j的衣服都不同,即第i件衣服在这个区间我们只穿一次,那么dp[i][j]=dp[i+1][j]+1;
否则遍历k(i<k<=j),如果a[k]==a[i],那么对于第k个party,可以穿第i件衣服,此时dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX = 110;
int a[MAX], n;
int dp[MAX][MAX];
int main() {
int t, cas = 1;
scanf("%d\n", &t);
while (t--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
dp[i][j] = j-i+1;
for (int i = n-1; i >= 1; i--)
for (int j = i+1; j <= n; j++)
{
dp[i][j] = dp[i+1][j] + 1;
for (int k = i; k <= j; k++)
if (a[i] == a[k])
dp[i][j] = min(dp[i][j], dp[i+1][k-1]+dp[k][j]);
}
printf("Case %d: %d\n", cas++, dp[1][n]);
}
return 0;
}
总结
区间DP和线性DP还是很相似的,线性dp是以一个点为对象,建立状态方程,在整个区间中找到最优解; 而区间dp则是以一个区间为对象,找出边界条件从小区间开始不断求解出最终总的区间问题。
区间DP主要是把一个大区间拆分成几个小区间,先求小区间的最优值,然后合并起来求大区间的最优值。