一般背包问题中dp表示的都是题目中让求解的量
1.求1-n拆分成两个相等的子集合的个数
思路:典型的0-1背包问题
设dp[i][j]为1-i个数字当中可以组成和为j的个数,
有两个选择,选择i或者不选择i
选择i dp[i][j]=dp[i][j-i]
不选择i dp[i][j]=dp[i-1][j-i]
状态转移方程
所以dp[i][j]=dp[i][j-i]+dp[i-1][j-1]
由于只是i取决于上一个值所以可将状态转移方程转换成
dp[j]=dp[j-i]+dp[j-1]
初始状态
dp[0]=1
int main()
{
int n;
cin >> n;
int sum = n * (n + 1) / 2;
if (sum % 2!= 0)
{
cout << "0" << endl;
return 0;
}
sum = sum / 2;
for (int i = 1; i <= n; i++)
dp[i] = 0;
dp[0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = sum; j >= i; j--)
{
dp[j] += dp[j - i];
}
}
cout << dp[sum]/2 << endl;
return 0;
}
2.点菜问题
输入C和N,C表示的是报销价格,N表示的菜品个数
接下来的N行每行输入两个数,一个数是菜的价格,一个数是菜的评价分数
输出在报销价格范围内所点的菜的最大评价分数
分析:
dp[i][j]表示前i个菜中可以得到的价值最大数,j表示的钱的数量
在当前菜品中,可以选择菜品i 也可以不选i
选择i dp[i][j]=dp[i-1][j-p[i]]+v[i]
不选择i dp[i][j]=dp[i-1][j]
状态转移方程
dp[i][j]=max(dp[i-1][j-p[i]]+v[i],dp[i-1][j])
dp[j]=max(dp[j-p[j]]+v[i],dp[j])
初始条件
dp[…]=0
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN = 100;
struct food
{
int price;
int value;
};
food arr[MAXN];
int dp[MAXN];
int c, n;
int main()
{
cin >> c >> n;
for (int i = 0; i < n; i++)
{
cin >> arr[i].price >> arr[i].value;
}
for (int i = 0; i < c; i++)
dp[i] = 0;
for (int i = 0; i < n; i++)
{
for (int j = c; j >= arr[i].price; j--)
{
dp[j] = max(dp[j], dp[j - arr[i].price] + arr[i].value);
}
}
cout << dp[c] << endl;
return 0;
}
3.求解添加最少括号数的问题
括号序列由(){} [],但是(}{),(}(}不合法 ,编写程序求出这些不合法的括号序列至少需要添加几个括号
例如(}(} 需要添加四个括号变成合法的
分析:(只是根据下面的代码整理的思路,有些问题依然不是很明白,比如为什么最后还要有for循环,不加这个for循环行不行?不行,运行结果是错误的)
dp[i][j]表示的是从i到j至少需要的括号数量
如果s[i]与s[j]匹配了,那么dp[i][j]=dp[i][j-1]-1;
如果s[i]与s[j]不匹配
s[j]都是左括号( { [ ,那么 说明i-- j-1一定不会有括号和它匹配
dp[i][j]=dp[i][j-1]+1
s[j]右括号,不与s[i]匹配,这只是说明s[i]与s[j]一定不匹配,那么i+1 --j-1 可能会有和s[j]匹配的值
dp[i][j]=dp[i+1][j]+1
最后
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])
int func(string str){
int ans;
for (int i = 0; i < str.size(); i++)
{
dp[i][i] = 1;
}
for (int len = 1; len < str.size(); len++)
{
for (int i = 0; i < str.size() - len; i++)
{
int j = len + i;
dp[i][j] = INF;
if (str[i] == '(' && str[j] == ')' || str[i] == '[' && str[j] == ']' || str[i] == '{' && str[j] == '}')
{
dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]);
//cout << dp[i][j] << endl << dp[i + 1][j - 1] << endl;
}
//下面的两个if代码与给出的标准答案略有不同,因为标准答案不太理解,这个运行出来也是正确的,我也是可以理解的
else if (str[j] == '(' || str[j] == '{' || str[j] == '[')
dp[i][j] = min(dp[i][j], dp[i][j-1] + 1);
else if (str[j] == ')' || str[j] == '}' || str[j] == ']')
dp[i][j] = min(dp[i][j], dp[i+1][j] + 1);
for (int k = i; k < j; k++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
}
}
return dp[0][str.size() - 1];
}
4.求解小易喜欢的数列问题
1,k ,n 表示数列在1-k内,共n个数
2, 数列内,A<=B或者AmodB!=0
分析:
完全背包问题
状态转移公式
dp[i][j]=dp[i-1][m] (m在1—j-1之间) 表示的是有k个数但是以j 结尾的数列的合法个数
因为直接计算需要的是三层for循环,会超时所以可以这样计算
先计算选取i-1个数字的时候合法数列的总数sum,
而j加入到i-1个数后面的时候非法的数列的个数invalid
dp[i][j]=sum-invalid
初始状态
dp[1][n]=1 //以n结尾只有1个数字的数列只有1个
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= k; i++)
dp[1][i] = 1;
for (int i = 2; i <= k; i++)
{
//首先计算一下dp[i-1][...]一共有多少
int sum = 0;
for (int x
= 1; x <= k; x++)
sum += dp[i - 1][x];
for (int j = 1; j <= k; j++)
{
int m = 1;
int invalid = 0;
while (m <= k)
{
if (!(m <= j || m % j != 0))
invalid += dp[i - 1][m];
m++;
}
dp[i][j] = sum - invalid;
}
}
int ans = 0;
for (int i = 1; i <= k; i++)
{
ans += dp[n][i];
}
cout << ans << endl;
return 0;
}
需要注意的问题
1.invalid的计算,在m后面加上j不合法的时候,其不合法的数量为dp[i-1][m]个
所以是invalid+=dp[i-1][m]
2. 最后的答案是dp[n][…] 而不是dp[n][k]
因为让计算的是在1-k的数字之内数列长度为n的满足条件的数列个数
dp[n][k]计算的是以k结尾的数列长度为n的数列,这只是其中一个情况,还有dp[n][k-1],dp[n][k-2],这都满足在1-k之内数列长度为n的条件
5求解硬币分配问题
典型的完全背包问题
现有1‘ 2’ 5‘硬币,求解组成10’硬币的组合方案数
分析:
dp[i][j]表示前i个数字j‘的个数
选i dp[i][j]=dp[i-1][j-v[i]]
不选i dp[i][j]=dp[i-1][j]
dp[i][j]=dp[i-1][j-v[i]]+dp[i-1][j]
dp[j]=dp[j-v[i]]+dp[j]
int main()
{
int ans = 0;
//对dp初始化
dp[0] = 1;
for (int i = 0; i < 3; i++)
{
for (int j = arr[i]; j <= p; j++)
dp[j] = dp[j - arr[i]] + dp[j];
}
cout << dp[p] << endl;
return 0;
}