//一般区间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为状态转移方程数组,a为数据数组(下标从1开始),sum为数据前缀和数组
假设a[] = {1,2,3,4}, sum[] = {1,3,6,10}
一条直线上有N堆石子,现在要将所有石子合并成一堆,每次只能合并相邻的两堆,合并花费为新合成的一堆石子的数量,求最小的花费。
1堆,花费为0
2堆,花费为sum[2]
3堆,花费为min(a[1] + a[2], a[2] + a[3]) + sum[3]
如果我们有n堆,合并的最后一次操作一定是从两堆合并成一堆,
规定dp[i][j]为合并第i堆到第j堆的最小花费
DP方程为:
dp[i][j] = min(dp[i][k] + dp[k+1][j]) + sum[j] - sum[i-1] (i <= k < j)
//复杂度O(n^3) 可以用平行四边形优化到O(n^2)
//http://blog.csdn.net/find_my_dream/article/details/4931222
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] + sum[j] - sum[i-1])
dp[i][j] = dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1];
}
}
}
printf("%d\n", dp[1][n]);
给一个括号组成的字符串,问最多能匹配多少个括号
像([)]这样的字符串匹配度为2,但是像()[]、[()]的字符串匹配度为4,也就是说括号具有分隔作用。
长度为1的串匹配度 0
长度为2的串匹配度 0 或 2
规定dp[i][j]为合并第i到第j个字符的最大匹配度
长度为n时,我们可以先检测a[i]和a[j]是否匹配,
匹配dp[i][j] = dp[i+1][j-1] + 2
不匹配,那我们可以按第一种模型处理,从任意位置分成两个区间
while (gets(a+1))
{
if (a[1] == 'e') break;
memset(dp, 0, sizeof(dp));
int n = strlen(a+1);
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,j]区间的字符当成由[i+1,j]在前面加个字符或[i,j-1]在后面加一个字符得来的
这里我们只考虑[i,j]由[i+1,j]在前面加一个字符的情况
如果a[i+1]到a[j]没有和a[i]匹配的,那么dp[i][j] = dp[i+1][j]
如果a[k]和a[i]匹配(i < k <= j),那么dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2);
比如:[xxxxx]yyyyy通过括号分成两个子串
//代码还可以这样写
while (gets(a+1))
{
if(a[1] == 'e') break;
memset(dp, 0, sizeof(dp));
int n = strlen(a+1);
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]);
}
看两道题体会下这种模型吧 ^-^
Halloween Costumes LightOJ - 1422和这道差不多
题意:一群小屌丝,排成一排,第k个上场屌丝值就为a[i]*(k-1),可以通过一个小黑屋(栈)调整顺序,但是先进屋的最后才能出来,求屌丝值和最小。
思路:首先,我们知道栈有顺序,比如1、2、3都进栈,出栈就一定是3、2,1了。
规定dp[i][j]为从第i个人到第j个人出场的最小屌丝值。
如果第i个人第k个上场(1 <= k <= j-i+1),那么[i+1,i+k-1]区间的人一定在i上场前都上场了,这时候刚好栈空,前k个人全上场了,然后就能以i的上场次序划分区间了??
DP方程:
dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + a[i]*(k-1) + dp[i+k][j] + (sum[j] - sum[i+k-1])*k)
memset(dp, 0, sizeof(dp)); //会访问到dp[j+1][j]这样的区间,赋值成0就好了
for (int i = 1; i <= n; i++)
for (int j = i+1; j <= n; j++)
dp[i][j] = INF;
for (int len = 2; len <= n; len++)
{
for (int i = 1, j = len; j <= n; i++, j++)
{
for (int k = 1; k <= j-i+1; k++)//k的枚举也可以是[i,j],不过等待时间要做些改变
dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + a[i]*(k-1) + dp[i+k][j] + (sum[j]-sum[i+k-1])*k);
}
}
这道题要先是用区间dp预处理,就是把空白串变成串t的最小花费,然后再计算由串s到串t的最小花费,看代码吧。
#include
#include
#include
using namespace std;
const int MAXN = 105;
char s[MAXN],t[MAXN];
int dp[MAXN][MAXN];
int a[MAXN];
int main()
{
while (~scanf(" %s %s", s+1,t+1))
{
int n = strlen(s+1);
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
dp[i][i] = 1;
for (int len = 2; len <= n; len++)
{
for (int i = 1, j = len; j <= n; i++, j++)
{
dp[i][j] = 1 + dp[i+1][j]; //前面增加的字符需要修改一次
for (int k = i+1; k <= j; k++)
if (t[i] == t[k])
dp[i][j] = min(dp[i][j], dp[i+1][k-1] + dp[k][j]);
}
}
for (int i = 1; i <= n; i++)
{
a[i] = dp[1][i];
if (s[i] == t[i])
a[i] = a[i-1];
else
{
for (int j = 1; j <= i; j++)
a[i] = min(a[i], a[j] + dp[j+1][i]);
}
}
printf("%d\n", a[n]);
}
return 0;
}
这种比较简单,不需要枚举区间k∈[i,j],这种类型只和左右边界相关。
题意:n个字符组成长度为m的字符串,给出增删字符的花费,可在字符串任意位置增删字符,求把字符串修改成回文串的最小花费。
规定dp[i][j]为将[i,j]区间改成回文串的最小花费,可以看成有回文串
[i+1,j]在前面加一个字符 =>前面删或后面增
[i,j-1]在后面加一个字符 =>前面增或后面删
共四种情况,当a[i] == a[j]时,加个dp[i+1][j-1]的情况就好了
#include
#include
#include
using namespace std;
const int MAXN = 2005;
char a[MAXN], ch;
int dp[MAXN][MAXN];
int add[30],sub[30];
int main()
{
int n, m;
scanf("%d %d", &n, &m);
scanf("%s", a+1);
for (int i = 1; i <= n; i++)
{
scanf(" %c", &ch);
scanf("%d %d", &add[ch-'a'], &sub[ch-'a']);
}
memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= m; i++)
dp[i][i] = 0;
for (int len = 2; len <= m; len++)
for(int i = 1, j = len; j <= m; i++, j++)
{
dp[i][j] = min(dp[i][j], min(add[a[i]-'a'],sub[a[i]-'a']) + dp[i+1][j]);
dp[i][j] = min(dp[i][j], dp[i][j-1] + min(add[a[j]-'a'],sub[a[j]-'a']));
if (a[i] == a[j])
{
if (len==2)
dp[i][j] = 0;
else
dp[i][j] = min(dp[i][j], dp[i+1][j-1]);
}
}
printf("%d\n", dp[1][m]);
return 0;
}
题意:田忌赛马,赢一场得200,负一场输200,问最后最多能赢多少钱(or输的最少)
思路:假设齐王从最强的马开始出,如果田忌最强的马能赢齐王最强的,就用最强的马,不能赢,就能最弱的马。
是不是很像贪心?但是处理最强的打平比较麻烦,可以选择平或者选择输。
比如田忌的马速度 2,3 齐王 1,3,这样速度3的打平,速度2的胜一场。
田忌的马速度 1,2,3,4 齐王的马速度 1,2,3,4,速度1的输给4,其他三场胜。
但是我们能分析出每次要么派最强的上场,要么派最弱的上场。
这里只讨论DP的方法。
规定dp[i][j]为田忌第i到第j匹马和齐王慢的j-i+1匹马比的胜场减负场值
我们继续看这个例子:田忌的马速度 1,2,3,4 齐王的马速度 1,2,3,4
dp[1][3]为田忌速度为1,2,3的马和齐王速度为1,2,3的马比
dp[2][4]为田忌速度为2,3,4的马和齐王速度为1,2,3的马比
dp[1][4]为田忌速度为1,2,3,4的马和齐王速度为1,2,3,4的马比
我们可以把他看成[1,3]加了一匹速度4的马,[2,4]加了一匹速度1的马
齐王都是加了速度4的马。
这时候就有两种选择:
1.让最强的马(速度4)和齐王的强马比
2.让最慢的马(速度1)和齐王的强马比
#include
#include
#include
#include
using namespace std;
const int MAXN = 1005;
int dp[MAXN][MAXN];
int tian[MAXN], qi[MAXN];
int val(int a, int b)
{
if (a > b) return 1;
if (a < b) return -1;
return 0;
}
int main()
{
int n;
while (~scanf("%d", &n) && n)
{
for (int i = 1; i <= n; i++) scanf("%d", &tian[i]);
for (int i = 1; i <= n; i++) scanf("%d", &qi[i]);
sort(tian+1, tian+n+1);
sort(qi+1, qi+n+1);
memset(dp, 0x8f, sizeof(dp));
for (int i = 1; i <= n; i++)
dp[i][i] = val(tian[i], qi[1]);
for (int len = 2; len <= n; len++)
for (int i = 1, j = len; j <= n; i++, j++)
dp[i][j] = max(dp[i][j-1] + val(tian[j], qi[len]), dp[i+1][j] + val(tian[i], qi[len]));
printf("%d\n", dp[1][n] * 200);
}
return 0;
}