//一般区间DP实现代码
memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= n; i++) //区间长度为1的初始化
dp[i][i] = 0;
for (int len = 2; len <= n; len++) //枚举区间长度
{
for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
{
//DP方程实现
}
}
dp[i][j] i 是左区间 j是右区间
memset(dp,0,sizeof(dp))//初始dp数组
for(int len=2;len<=n;len++){//枚举区间长度
for(int i=1;i<n;++i){//枚举区间的起点
int j=i+len-1;//根据起点和长度得出终点
if(j>n) break;//符合条件的终点
for(int k=i;k<=j;++k)//枚举最优分割点
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
}
}
例题1:--------------------------------------------------------------------------------------
【题目描述】
在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
计算出将N堆石子合并成一堆的最小得分。
【输入】
第一行为一个正整数N (2≤N≤100);
以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1≤i≤N)。
【输出】
一个正整数,即最小得分。
【输入样例】
7
13
7
8
16
21
4
18
【输出样例】
239
思路:
不同的合并方式最后的结果也不同所以利用区间来考虑合并;dp[i][j]表示 合并i----j这个区间内的所有石子所花费的最小值
那我们可以再去把 i–j细分啊 再去分成 i—k 和k+1----j 然后去跟dp[i][j]比较 赋给它最小值
所以 dp式子为
dp[i][j]=dp[i][k]+dp[k+1][j]+a[j]-a[i-1];
这里需要注意前缀和,每个值都为 a[i]+=a[i-1];
比方说你想求 2-3的和 s=a[3]-a[2-1];
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;
long long a[105], dp[105][105];
int main()
{
int n; cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i]; a[i] += a[i - 1];
}
//dp[i][j] 表示合并i-j堆花费的最少费用
memset(dp, 0x3f, sizeof dp);//初始化的最大值写法
for (int i = 1; i <= n; i++)
dp[i][i] = 0;
for (int len = 2; len <= n; len++)
{
for (int i = 1, j = len; j <= n; i++,j++)
{
for (int k = i; k <= j; k++)
{
if (dp[i][j] > dp[i][k] + dp[k + 1][j] + a[j] - a[i - 1])
dp[i][j] = dp[i][k] + dp[k + 1][j] + a[j] - a[i - 1];
}
}
}
cout << dp[1][n] << endl;
}
例题二:------------------------------------------------------------------------------------
括号匹配
poj2955
给一个括号组成的字符串,问最多能匹配多少个括号
像([)]这样的字符串匹配度为2,但是像()[]、[()]的字符串匹配度为4,也就是说括号具有分隔作用。
长度为1的串匹配度 0
长度为2的串匹配度 0 或 2
思路①
dp[i][j]为i–j 之间的最大匹配度
如果 i和j上的匹配 那么dp[i][j]=dp[i+1][j-1]+2;
否则
就再去分割区间 类似于上一个例题。
for (int len = 2; len <= n; len++)
{
for(int i = 1, j = len; j <= n; i++, j++)
{
if((a[i]=='('&&a[j]==')') || (a[i]=='['&&a[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];
}
}
printf("%d\n",dp[1][n]);
}
思路②
涉及到了 第二种模型
第二种模型就是根据匹配信息把区间划分成[i+1,k-1]和[k+1,j]
for (int len = 2; len <= n; len++)
{
for(int i = 1, j = len; j <= n; i++, j++)
{
dp[i][j] = dp[i+1][j];
for (int k = i; k <= j; k++)
if((a[i]=='('&&a[k]==')') || (a[i]=='['&&a[k]==']'))
dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2);
}
}
printf("%d\n",dp[1][n]);
例题3
题意:有N个宴会,对于每一个宴会,女猪脚都要穿一种礼服,礼服可以套着穿,但是脱了的不能再用,参加宴会必须按顺序来,从第一个到第N个,问参加这些宴会最少需要几件礼服,拿第一个案例来说
4
1 2 1 2,有4个宴会,第一个需要礼服种类为1,第二个需要礼服种类为2,以此往下推:
参加第一个宴会时穿礼服1,参加第二个时,礼服1不要脱下,直接把礼服2套在外面,参加第三个的时候把礼服2脱下即可,参加第四个则需要一件新的礼服了
思路:
dp[i][j]代表从区间i到区间j最少的穿衣数量,那么在dp[i][j]这个状态的穿衣数,就要等于dp[i+1][j]+1;也就是说,首先在不考虑它后面是否有一天要穿相同的衣服的情况下,它肯定会比区间i+1到j的衣服多出一件;
然后,再考虑在这个区间范围,是否有一天要穿相同的衣服,i<k<=j,如果有第k天衣服和第i天的衣服是一样的,那么就要比较如果第i天不穿1件衣服与第i天穿上1件衣服;
首先,第i天穿上一件衣服的结果已经得出,那么我们只需比较不穿衣服,那么就是dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <math.h>
#include <stdio.h>
using namespace std;
int n;
int a[105];
int dp[105][105];
int main()
{
int t;
scanf("%d",&t);
int cas=0;
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(dp,0,sizeof(dp));
for(int i=n;i>=1;i--)
{
for(int j=i;j<=n;j++) 从后往前枚举,并且后面的最优解已经枚
举过了,因为实际是从前往后进行,所以从后枚举不会造成影响
{
dp[i][j]=dp[i+1][j]+1;
先直接给他加上,因为说不
定这件衣服后面好多件都要用到
for(int k=i+1;k<=j;k++)
{
if(a[i]==a[k])
dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]); //这里不要是i+1,不是i,因为在讨论是不是脱i呢
}
}
}
printf("Case %d: %d\n",++cas,dp[1][n]);
}
return 0;
}