目录
知识框架
No.0 筑基
请先学习下知识点,阁下!
题目大部分来源于此:代码随想录:动态规划
No.1 层数普通dp
题目来源:LeetCode-509-斐波那契数列
题目描述:
题目思路:
按照dp的五部曲进行分析即可;同时注意临界条件;
题目代码:
class Solution {
public:
int fib(int N) {
if (N <= 1) return N;
vector<int> dp(N + 1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= N; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[N];
}
};
题目来源:LeetCode-70-爬楼梯
题目描述:
题目思路:
动态规划五部曲
题目代码:
class Solution {
public:
int climbStairs(int n) {
if(n<=1)return n;
vector<int>dp(n+1);//表示 方案数量
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
题目来源:LeetCode-746-使用最小花费爬楼梯
题目描述:
题目思路:
五部曲,然后这个主要是动态规划的状态转移方程式:还是按照题意进行最顺畅
题目代码:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n=cost.size();
if(n<=1)return 0;
vector<int>dp (n+1);
//从下标0开始
dp[0]=0;
dp[1]=cost[0];
for(int i=2;i<=n;i++){
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
int minn=dp[n];
//从下标1开始
dp[0]=0;
dp[1]=0;
for(int i=2;i<=n;i++){
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
dp[n]=min(minn,dp[n]);
return dp[n];
}
};
题目来源:蓝桥杯-第六届-垒骰子
题目描述:
题目思路:
题目代码:
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
#define mod 1000000007
long long n;
long long f[N];
long long res;
long long dp[N][7];//dp[i][j] 表示 第i层 数字j 朝上的方案数量。
int m,k,g,d;
int x,y,z,t;
char ch;
string str;
vector<int>v[N];
int main() {
//垒骰子
memset (dp, false, sizeof dp);
cin>>n>>m;
map<int,int>mp;
map<int,int>fanmian;
fanmian[1]=4;
fanmian[4]=1;
fanmian[2]=5;
fanmian[5]=2;
fanmian[3]=6;
fanmian[6]=3;
while(m--){
cin>>x>>y;
mp[x]=y;
mp[y]=x;
}
dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=dp[1][5]=dp[1][6]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=6;j++){
int dui=fanmian[j];
for(int q=1;q<=6;q++){
if(q==mp[dui])continue;
dp[i][j]+=dp[i-1][q]%mod;
}
}
}
res=(dp[n][1]+dp[n][2]+dp[n][3]+dp[n][4]+dp[n][5]+dp[n][6])%mod;
res=res*pow(4,n);
res=res%mod;
cout<<res<<endl;
return 0;
}
题目来源:蓝桥杯-第九届-测试次数
题目描述:
题目思路:
题目代码:
#include<iostream>
#include<algorithm>
using namespace std;
int f[5][1005];
int main()
{
for(int i = 1; i <= 3; i ++)
for(int j = 1; j <= 1000; j ++)
f[i][j] = j; // 无论有几部手机,运气最差时的测试次数就是楼层的高度
// (第一部手机从第一层摔到最后一层,都不坏)
for(int i = 2; i <= 3; i ++)
for(int j = 1; j <= 1000; j ++)
for(int k = 1; k < j; k ++)
f[i][j] = min(f[i][j], max(f[i- 1][k - 1], f[i][j - k]) + 1); // min 表示最佳策略,max 表示最差运气
cout << f[3][1000] << endl;
return 0;
}
题目来源:蓝桥杯-第九届-搭积木
题目描述:
转自这里:搭积木解题链接
题目思路:
题目代码:
No.2 长度DP
题目来源:LeetCode-5. 最长回文子串
题目描述:
5. 最长回文子串
题目思路:
首先是动态规划;;然后也可以在中间进行判断;即遍历然后每个当次中间的;然后判断两边的是不是一样的;(假设奇数和偶数考虑)
题目代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if (n < 2) return s;
int maxLen = 1;
int begin = 0;
// 第一步先定义
// dp[i][j] 表示 s[i..j] 是否是回文串
vector<vector<int>> dp(n, vector<int>(n));
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
// 递推开始
// 先枚举子串长度
for (int L = 2; L <= n; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < n; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = i+L - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= n) {
break;
}
if (s[i] != s[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substr(begin, maxLen);
}
};
// 这个称呼为中心转移法;
class Solution {
public:
pair<int, int> expandAroundCenter(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return {left + 1, right - 1};
}
string longestPalindrome(string s) {
int start = 0, end = 0;
for (int i = 0; i < s.size(); ++i) {
//考虑奇数和偶数:
auto [left1, right1] = expandAroundCenter(s, i, i);
auto [left2, right2] = expandAroundCenter(s, i, i + 1);
if (right1 - left1 > end - start) {
start = left1;
end = right1;
}
if (right2 - left2 > end - start) {
start = left2;
end = right2;
}
}
return s.substr(start, end - start + 1);
}
};
题目来源:PTA-L3-020 至多删三个字符
题目描述:
题目思路:
题目代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 9;
ll dp[N][5];
char s[N];
int main()
{
cin >> (s+1);
int n = strlen(s+1);
dp[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= 3; j++)
{
dp[i][j] = dp[i - 1][j];
if(j)
dp[i][j] += dp[i - 1][j - 1];
for(int k = i-1;k>=1&&(i-k)<=j;k--)
{
if(s[k]==s[i])
{
dp[i][j] -= dp[k-1][j-(i-k)];
//[j-(i-k)] k-1 删了几个字符
break;
}
}
}
}
ll ans = 0;
for(int i = 0;i<=3;i++)
ans += dp[n][i];
cout<<ans<<"\n";
}
题目来源:LeetCode-343-整数拆分
题目描述:
题目思路:
题目代码:
class Solution {
public:
int integerBreak(int n) {
//dp
//先定义表示 dp[i] i的乘积最大化
int dp[n+1];
//初始化
dp[1]=1;
dp[2]=1;
//状态转移方程:
// dp[n]=max(dp[n-1]*1,dp[n-2]*2,.....);好像涉及到选还是不选了吗?
for(int i=3;i<=n;i++){
dp[i]=dp[i-1];
for(int j=1;j<i;j++){
// 这个还是不是很理解 多了的 j*(i-j);例子是
//也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
dp[i]=max({dp[i],j*(i-j),dp[i-j]*j});
}
}
return dp[n];
}
};
题目来源:Acwing-3652- 最大连续子序列(模板题)
题目描述:
题目思路:
一开始想的是前缀和,但是呢,它会超时,所以要dp
题目代码:
//超时版本:
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
int n,m,k,g,d;
int x,y,z;
char ch;
string str;
vector<int>v[N];
int main() {
while (cin>>k)
{
int num[k+1]={0};
int sum[k+1]={0};
for(int i=1;i<=k;i++){
cin>>num[i];
sum[i]=sum[i-1]+num[i];
}
int l,r;
int res=0;
int resl,resr;
//定左指针
for( l =1;l<=k;l++){
for( r=l;r<=k;r++){
if(sum[r]-sum[l-1]>res){
res=sum[r]-sum[l-1];
resl=l;
resr=r;
}
}
}
if(res==0)cout<<"0 0 0"<<endl;
else cout<<res<<" "<<resl-1<<" "<<resr-1<<endl;
}
return 0;
}
//动态规划版本:
//AC版本:
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
int n,m,k,g,d;
int x,y,z;
char ch;
string str;
vector<int>v[N];
int main() {
int f[N];//五部曲1: 定义:以i为结尾的连续子序列的最值(肯定选了第i个数值的即选中了第i个数值)
f[0]=0; //初始化:然后好像没有必要初始化的地方的,其实。
int res,resl,resr; //用来返回最终结果。
while(cin>>n){
res=-inf;
int a[n+1];
for(int i=1;i<=n;i++)cin>>a[i];
int l=0,r=0,temp=0;//用来记录选择序列的 前后下标;
for(int i=1;i<=n;i++){ //遍历顺序,从前往后;
f[i]=max(f[i-1]+a[i],a[i]);// f[i]表示的是肯定选择a[i]的,然后看这个前面的选择的是否为负的;
if(f[i]==f[i-1]+a[i]){
//说明是前面的>=0的,再续接上a[i];
l=l;
r=i-1;
}else if(f[i]==a[i]){
//说明是前面的小于0的,再续接上a[i];
l=i-1;
r=i-1;
}
if(f[i]>res){
res=f[i];
resl=l;
resr=r;
}
}
if(res<0)printf("0 0 0\n");
else printf("%d %d %d\n",res, resl , resr);
}
return 0;
}
题目来源:牛客网-NC21302
题目描述:
题目思路:
- 一个数能被3整除,说明这个数每位的和是3的倍数
- 利用动态规划的思想,dp[i][[j] 表示前i位,模3是j的数的个数。
题目代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define inf 0x3f3f3f3f
#define debug(x) cout<<#x<<" = "<<x<<endl;
#define LL long long
const int mod = 1e9+7;
int n,m,k,d,g;
int x,y,z;
string str;
char ch;
vector<int>v[N];
LL dp[55][4];
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>str;
//1. 一个数能被3整除,说明这个数每位的和是3的倍数
//2. 利用动态规划的思想,dp[i][[j] 表示前i位,模3是j的数的个数。
for(int i=1;i<=str.size();i++)
{
x=str[i-1]-'0';
if(x%3==0){
// 这里的1 表示只选择第i个位置这一个数;,
//第一个dp[i-1][0]表示不选,第二个表示选上再加上前面的;
dp[i][0]=(1+dp[i-1][0]+dp[i-1][0])%mod;
dp[i][1]=(dp[i-1][1]+dp[i-1][1])%mod;
dp[i][2]=(dp[i-1][2]+dp[i-1][2])%mod;
}
if(x%3==1){
dp[i][0]=(dp[i-1][0]+dp[i-1][2])%mod;
dp[i][1]=(1+dp[i-1][1]+dp[i-1][0])%mod;
dp[i][2]=(dp[i-1][2]+dp[i-1][1])%mod;
}
if(x%3==2){
dp[i][0]=(dp[i-1][0]+dp[i-1][1])%mod;
dp[i][1]=(dp[i-1][1]+dp[i-1][2])%mod;
dp[i][2]=(1+dp[i-1][2]+dp[i-1][0])%mod;
}
}
cout<<dp[str.size()][0]<<endl;
return 0;
}
题目来源:蓝桥杯-第十届-最优包含
题目描述:
题目思路:
由于可以组成特别多的序列,因此我们使用动态规划思想来简化该过程。
我们设dp[i][j]表示S串中前i个字符,包含有T串中前j个字符最少需要修改的字符个数。
因此分析得到:
如果S[i]=T[j] ,那么T串中的最后一位要么让他和S[i]相等,要么让他和前面的相等。
dp[i][j] = min(dp[i-1][j],dp[i-1][j-1]);
如果S[i]!=T[j],那么要么是让T[j]和S串前面的字符一样,要么修改S[i]。
dp[i][j]= min(dp[i-1][j-1]+1,dp[i-1][j])
并且注意的是这里动态方程中我们的i,j为个数的意思,转换为代码时由于下标从0开始,所以实际上要大一。
题目代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+50;
char a[N],b[N];
int dp[N][N];
int main(){
cin>>a+1>>b+1;
int la=strlen(a+1);
int lb=strlen(b+1);
memset(dp,0x3f3f3f3f,sizeof dp);
dp[0][0]=0;
for(int i=1;i<=la;i++)
{
dp[i][0]=0;
for(int j=1;j<=lb;j++)
{
if(a[i]==b[j])
dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]);
else
dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]+1);
}
}
cout<<dp[la][lb];
return 0;
}
题目来源:蓝桥杯-第十届-排列数
题目描述:
转自这里:排列数的题解
题目思路:
题目代码:
题目来源:蓝桥杯-2013省赛-买不到的数目
题目描述:
题目思路:
题目代码:
#include <iostream>
using namespace std;
const int MAX=1e5;
int x[2];
int maxy;
bool dp[MAX];
int main()
{
//int x,y;
//cin>>x>>y;
//cout<<x*y-x-y; //好像因为什么因素直接这样输出就可以了
dp[0]=1;
for(int i=0;i<2;i++){//外层是一包糖数转换
cin>>x[i];
dp[x[i]]=1;
for(int j=dp[i];j<MAX;j++){//内层是增长的数
//dp[j]=dp[j]|dp[j-x[i]];
if(dp[j]){dp[j+x[i]]=1;}
if(!dp[j]){maxy=j;}
}
}
cout<<maxy;
return 0;
}
题目来源:蓝桥杯-2017省赛-对局匹配
题目描述:
题目思路:
题目代码:
#include<iostream>
#include<algorithm>
using namespace std;
int N, K, dp[100001];
int cnt[100001] = {0};
int main() {
int tmp, max_num = 0;
size_t res = 0;
cin >> N >> K;
for (int i = 0; i < N; ++i) {
cin >> tmp;
++cnt[tmp];
max_num = max(max_num, tmp);
}
// K = 0 时, 只需计算一共有多少种不同的数即可
if (K == 0) {
for (int i = 0; i <= max_num; ++i) {
if (cnt[i] != 0) {
++res;
}
}
} else {
// 把数据分成 K 组
for (int i = 0; i < K; ++i) {
dp[i] = cnt[i];
dp[i + K] = max(cnt[i], cnt[i + K]);
int j;
for (j = i + 2 * K; j <= max_num; j += K) {
dp[j] = max(dp[j - K], dp[j - 2 * K] + cnt[j]);
}
res += dp[j - K];
}
}
cout << res << endl;
return 0;
}
题目来源:蓝桥杯-2018省赛-乘积最大
题目描述:
题目思路:
题目代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010,M =1000000009;
typedef long long ll;
ll a[N];
int n, k;
ll res = 1;
int main()
{
cin >> n >> k;
for(int i = 0;i < n; i++)
{
cin >> a[i];
}
sort(a,a+n);
int l = 0,r = n - 1;
int sigh = 1; //符号标记
while(k % 2) //当k为奇数是,至少取一个最大的数,k--就是偶数则就偶数的求法取求
{
res = a[r];
r--;
k--;
if(res < 0) sigh = -1;
}
//双指针求法
while(k)
{
ll x = a[l] * a[l+1];
ll y = a[r] * a[r-1];
if(x * sigh > y * sigh)
{
res = x % M * res % M;
l += 2;
}
else
{
res = y % M * res % M;
r -= 2;
}
k -= 2;
}
cout << res << endl;
// 请在此输入您的代码
return 0;
}
题目来源:蓝桥杯-2022省赛-最大和
题目描述:
题目思路:
题目代码:
#include<bits/stdc++.h>
using namespace std;
const int MIN = -0x3f3f3f3f;
const int N = 1e5+10;
int a[N];
int f[N];
bool is_primer(int x)//判断是否为质数
{
for(int i = 2;i<=x/i;i++)
{
if(x%i==0)return false;
}
return true;
}
int find(int x)//寻找最小质因数
{
if(x==0)return 0;
if(x==1)return 1;
for(int i = 2;i<=x/i;i++)
if(((x%i)==0)&&is_primer(i))return i;
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i<=n;i++)scanf("%d",&a[i]);
memset(f,MIN,sizeof f);//将所有状态初始化为负无穷
f[1]=a[1];//走到第一格的最大分值为第一个宝物的分值
for(int i = 1;i<=n;i++)
{
int x = i + find(n-i);
for(int j = i+1;j<=x;j++)//枚举从下一格到D(x)的所有走法
{
f[j] = max(f[j],f[i]+a[j]);
}
}
cout << f[n] << endl;
return 0;
}
题目来源:蓝桥杯-2022省赛-李白打酒加强版
题目描述:
题目思路:
题目代码:
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll MOD=1000000007;
ll n,m,ans;
ll f[105][105][105];
int main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(false);
cin>>n>>m;
f[0][0][2]=1;//不管怎么样必然会有一种初始方案。
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=m;k++){
//遇到花
if(j&&k)f[i][j][k]=(f[i][j][k]+f[i][j-1][k+1])%MOD;
//遇到店
if(i&&k%2==0)f[i][j][k]=(f[i][j][k]+f[i-1][j][k/2])%MOD;
}
}
}
//因为没有酒遇到花是不合法的,所以循环的时候没有执行f[][m][0]的所有情况
//因为最后一步必须是遇到花,所以f[n][m-1][1]的值等于我们想要的f[n][m][0]
cout<<f[n][m-1][1];
return 0;
}
题目来源:蓝桥杯-2022省赛-2022
题目描述:
题目思路:
题目代码:
#include<bits/stdc++.h>
using namespace std;
//2022 拆分为十个整数 几种方法
//dp法
typedef long long ll;
ll dp[11][2025];
int main()
{
dp[0][0]=1;//取0个数字 总和为0 的方案数;
for(int i=1;i<=2022;i++)//枚举要加入的数字
{
for(int j=10;j>=1;j--)//取j个数字 10-9-8----1跟着状态转换方程来走
{
for(int k=i;k<=2022;k++)
{
dp[j][k]+=dp[j-1][k-i];//10个数字拼成2022---->来源于9个数字拼成2022-最后要被加入的i 的方案数
}
}
}
cout<<dp[10][2022];
return 0;
}
No.3 平面DP
题目来源:LeetCode-62-不同路径
题目描述:
题目思路:
一定要小心 初始化;
题目代码:
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[n+1][m+1];//数值即题意;
// dp[i][j]=dp[i-1][j]+dp[i][j-1];
//初始化
for(int i=1,j=1;j<=m;j++){
dp[i][j]=1;
}
for(int j=1, i=1;i<=n;i++){
dp[i][j]=1;
}
for(int i=2;i<=n;i++){
for(int j=2;j<=m;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[n][m];
}
};
题目来源:LeetCode-63-不同路径 II
题目描述:
题目思路:
题目代码:
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int h=obstacleGrid.size();
int l=obstacleGrid[0].size();
//如果在起点或终点出现了障碍,直接返回0
if (obstacleGrid[h - 1][l - 1] == 1 || obstacleGrid[0][0] == 1)return 0;
//先定义
int dp[h+1][l+1];
memset(dp,0,sizeof(dp));
dp[0][0]=1;
//初始化
for(int i=1;i<l;i++){
if(obstacleGrid[0][i]==1){
break;
}else{
dp[0][i]=1;
}
}
for(int i=1;i<h;i++){
if(obstacleGrid[i][0]==1){
break;
}else{
dp[i][0]=1;
}
}
// 转移方程
for(int i=1;i<h;i++){
for(int j=1;j<l;j++){
if(obstacleGrid[i][j]==1){
dp[i][j]=0;
}else{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[h-1][l-1];
}
};
题目来源:蓝桥杯-第十四届模拟-第二期 矩阵的最小路径
题目描述:
题目思路:
题目代码:
// 答案:592
#include <bits/stdc++.h>
using namespace std;
const int m = 30, n = 60;
char mat[m + 1][n + 2]; // mat: matrix
int dp[m + 1][n + 1];
int main() {
for (int i = 1; i <= m; i ++) {
cin >> (mat[i] + 1);
}
for (int i = 1; i <= m; i ++) {
for (int j = 1; j <= n; j ++) {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + mat[i][j] - '0';
}
}
cout << dp[m][n] << endl;
return 0;
}
题目来源:蓝桥杯-2013省赛-格子刷油漆
题目描述:
题目思路:
题目代码:
在这里插入代码片
题目来源:蓝桥杯-2021省赛-覆盖
题目描述:
题目思路:
题目代码:
在这里插入代码片
No.4 树状dp
树状dp知识点链接博客园链接
题目来源:蓝桥杯-第六届-生命之树
题目描述:
题目思路:
总的来说就是 给你一个树,然后每个节点赋个权值,然后让你找一个连通区域,使得这个区域内的权值和 最大。然后用树状dp,,从最底层的树,然后逐渐到最上层的根节点,然后定义dp[i],和状态转移方程;再用dfs进行 写个函数 用来 使得 dp[i] 进行更新。
题目代码:
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
#define LL long long
int n,m,k,g,d;
int x,y,z,t;
char ch;
string str;
vector<int>v[N];
LL f[N];
LL res;
//从最底层开始 进行 计算,,然后递归到 根节点,,
void dfs(int bt,int fa){
for(int i=0;i<v[bt].size();i++){
int son=v[bt][i];
if(son!=fa){//遍历结点son的除父节点以外的所有结点,也就是遍历所有子树
dfs(son,bt);//接着向下一层子结点遍历
f[bt]=max(f[bt],f[bt]+f[son]);
}
}
}
int main() {
cin>>n;
int w[n+1];
for(int i=1;i<=n;i++){
cin>>w[i];
f[i]=w[i];
}
for(int i=1;i<n;i++){
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1,-1);
for(int i=1;i<=n;i++)res = max(res,f[i]);
cout<<res<<endl;
return 0;
}
题目来源:LeetCode-343-整数拆分
题目描述:
题目思路:
题目代码:
class Solution {
public:
int integerBreak(int n) {
//dp
//先定义表示 dp[i] i的乘积最大化
int dp[n+1];
//初始化
dp[1]=1;
dp[2]=1;
//状态转移方程:
// dp[n]=max(dp[n-1]*1,dp[n-2]*2,.....);好像涉及到选还是不选了吗?
for(int i=3;i<=n;i++){
dp[i]=dp[i-1];
for(int j=1;j<i;j++){
// 这个还是不是很理解 多了的 j*(i-j);例子是
//也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
dp[i]=max({dp[i],j*(i-j),dp[i-j]*j});
}
}
return dp[n];
}
};
No.5 状压DP
题目来源:蓝桥杯-2015省赛-铺瓷砖
题目描述:
题目思路:
题目代码:
No.n 背包问题筑基
请先学习下知识点,道友!
题目知识点大部分来源于此:闫氏DP分析法题目例题大部分来源于此:Acwing的各自背包问题
No.n+1 01背包
引言:内容介绍
关于 01 背包:如下图所示;因为有1个可选,即每个物品即处于两种状态:选和不选;
那么如果暴力进行选取的话,就是2^n 时间复杂度;不可行;进而才需要动态规划的解法来进行优化!也是从下面的往后面的进行推进的;
按照动态规划的五部曲进行 的话:
首先是1:dp数组的定义:dp[i] [j] 从前面的i个物件中选取,装在容量j 的所得到到最大价值。
2:确定递推关系式子:因为是两种情况,放和不放,即: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3:初始化:等等
4:遍历顺序
void test_2_wei_bag_problem1() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagweight = 4;
// 二维数组
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
// 初始化
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
test_2_wei_bag_problem1();
}
之前的模板
最简单的0-1背包问题:
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
int n,m,k,g,d;
int x,y,z;
char ch;
string str;
vector<int>v[N];
int v[N];
int w[N];
int f[N][N];
int main() {
//0-1背包
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
if(j>=v[i]){
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
//变成一位的问题:尽量用这个
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
int n,m,k,g,d;
int x,y,z;
char ch;
string str;
int v[N];
int w[N];
int f[N];
int main() {
//0-1背包
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
if(j<v[i]){
f[j]=f[j];
}else{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
}
cout<<f[m]<<endl;
return 0;
}
//完全背包问题::
#include<iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1 ; i <= n ;i ++)
{
cin>>v[i]>>w[i];
}
for(int i = 1 ; i<=n ;i++)
for(int j = 0 ; j<=m ;j++)
{
for(int k = 0 ; k*v[i]<=j ; k++)
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
cout<<f[n][m]<<endl;
}
//求能够达到最大的价值的方案数量,就是有好多种选择可以使得所选的物品价值最大;;
//背包求方案数::
路径跟踪 g[i][j]g[i][j]
状态表示g(i,j)g(i,j)—集合: 考虑前 i 个物品,当前已使用体积恰好是 j 的,且 价值 为最大的方案
状态表示g(i,j)g(i,j)—属性: 方案的数量 SumSum
状态转移g(i,j)g(i,j):
如果fi,j=fi−1,jfi,j=fi−1,j 且 fi,j=fi−1,j−v+wfi,j=fi−1,j−v+w 则 gi,j=gi−1,j+gi−1,j−vgi,j=gi−1,j+gi−1,j−v
如果fi,j=fi−1,jfi,j=fi−1,j 且 fi,j≠fi−1,j−v+wfi,j≠fi−1,j−v+w 则 gi,j=gi−1,jgi,j=gi−1,j
如果fi,j≠fi−1,jfi,j≠fi−1,j 且 fi,j=fi−1,j−v+wfi,j=fi−1,j−v+w 则 gi,j=gi−1,j−vgi,j=gi−1,j−v
初始状态:g[0][0] = 1
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int w[N], v[N];
int f[N][N], g[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++ i) cin >> v[i] >> w[i];
for (int i = 1; i <= n; ++ i)
{
for (int j = 0; j <= m; ++ j)
{
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
g[0][0] = 1;
for (int i = 1; i <= n; ++ i)
{
for (int j = 0; j <= m; ++ j)
{
if (f[i][j] == f[i - 1][j])
g[i][j] = (g[i][j] + g[i - 1][j]) % mod;
if (j >= v[i] && f[i][j] == f[i - 1][j - v[i]] + w[i])
g[i][j] = (g[i][j] + g[i - 1][j - v[i]]) % mod;
}
}
int res = 0;
for (int j = 0; j <= m; ++ j)
{
if (f[n][j] == f[n][m])
{
res = (res + g[n][j]) % mod;
}
}
cout << res << endl;
return 0;
}
//求具体的方案数;
//看凑零钱那个就行了;
题目来源:PTA-L3-001 凑零钱
题目描述:
题目思路:
题目代码:
#include<bits/stdc++.h>
using namespace std;
#define N 10001
int value[N]={0};
int dp[N][N]={0};
int choose[N][N]={0}; //在加之最大未N时候,第N件物品是否选择‘
bool cmp(int x,int y){
return x>y;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>value[i];
}
sort(value+1,value+1+n,cmp);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[i][j]=dp[i-1][j];
if(j>=value[i]){
dp[i][j]=max(dp[i][j] ,value[i]+ dp[i-1][j-value[i]]);
if( dp[i][j]==value[i]+ dp[i-1][j-value[i]]){
choose[i][j]=1;
}
}
}
}
if(dp[n][m]!=m){
cout<<"No Solution"<<endl;
return 0;
}
vector<int>ans;
for(int i=n,j=m ; i>=1&& j>=0 ; i--){
if(choose[i][j])
{
ans.push_back(value[i]);
j=j-value[i];
}
}
for(int i=0;i<ans.size();i++){
if(i==0)cout<<ans[i];
else cout<<" "<<ans[i];
}
return 0;
}
题目来源:PTA-L3-2 拼题A打卡奖励
题目描述:
题目思路:
题目代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 100100
int n,k,g,d;
int x,y,z;
long long m;
char ch;
string str;
int v[N];
int w[N];
int main() {
//0-1背包
cin>>n>>m;
int sumtimt=0;
int sumvalue=0;
for(int i=1;i<=n;i++){
cin>>v[i];
sumtimt+=v[i];
}
for(int i=1;i<=n;i++){
cin>>w[i];
sumvalue+=w[i];
}
if(sumtimt<=m){
cout<<sumvalue<<endl;
return 0;
}
int f[m+1]={0};//适者生存;;;;
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
if(j<v[i]){
f[j]=f[j];
}else{
f[j]=max(f[j], f[j-v[i]]+w[i]);
}
}
}
cout<<f[m]<<endl;
return 0;
}
题目来源:LeetCode-416-分割等和子集
题目描述:
题目思路:
题目代码:
class Solution {
public:
bool canPartition(vector<int>& nums) {
//01背包
// 将 数值同时 当作 物品价值 和 物品重量;看最大能否达到 sum/2;
int n=nums.size();
int sum=accumulate(nums.begin(),nums.end(),0);
if(sum%2==1)return false;
sum=sum/2;
//定义dp
int dp[10005];// 前i个数字,达到j的
memset(dp,0,sizeof(dp));
//初始化:这个可以想着那个 矩阵网格;
//遍历顺序
for(int i=0;i<n;i++){
for(int j=sum;j>=nums[i];j--){
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[sum]==sum)return true;
return false;
}
};
题目来源:蓝桥杯-第十届-质数拆分
题目描述:
题目思路:
题目代码:
//对于N要进行适应性的更改,对于字段错误
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define N 30100000
#define mod 1000000007
long long n;
long long f[N];
int res;
int m,k,g,d;
int x,y,z,t;
char ch;
string str;
vector<int>v[N];
vector<int>ans;
long long dp[2500][2500];
void is_zhi(){
int flag=0;
ans.push_back(2);
for(int i=3;i<=2019;i++){
flag=0;
for(int j=2;j<=sqrt(i);j++){
if(i%j==0){
flag=1;
break;
}
}
if(flag==0)ans.push_back(i);
}
}
int main() {
ans.push_back(0);
is_zhi();
n = ans.size();
dp[0][0]=1;//dp[i][j]:从前 i 个物品中选,且总体积恰好为 j 的方案的数量
for(int i=1;i<n;i++){
for(int j=0;j<=2019;j++){
// 选或者不选
j<prime[i] 相当于当前最后一个质数取不了 相当于只能用前i-1个
//j>=prime[j] 除了dp[i-1][j]可以取到j
//还能看dp[i-1][j-prime[i]]有多少种情况 这是算上了第i个质数的情况
dp[i][j]=dp[i-1][j];
if(j>=ans[i]){
dp[i][j]+= dp[i-1][j-ans[i]];
}
}
}
cout<<dp[n-1][2019];
return 0;
}
题目来源:蓝桥杯-第十四届模拟-第二期 质数拆分
题目描述:
题目思路:
题目代码:
No.n+2 完全背包问题
题目来源:蓝桥杯-第八届-包子凑数
题目描述:
题目思路:
扩展欧几里德变形的,有个定理。如果满足所有数的最大公约数不为1则有无穷个,否则都是有限个。然后利用完全背包就可以统计了
题目代码:
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 100005;
int arr[105], n, brr[N];
int gcd(int a, int b){
if(a%b==0) return b;
return gcd(b, a%b);
}
int main()
{
scanf("%d", &n);
for(int i=0; i<n; ++i){
scanf("%d", &arr[i]);
}
int g = arr[0];
for(int i=1; i<n; ++i){
g = gcd(g, arr[i]);
}
if(g != 1){
printf("INF\n");
return 0;
}
brr[0] = 1;
for(int i=0; i<n; ++i){
for(int j=arr[i]; j<N; ++j){
if(brr[j-arr[i]])brr[j] = 1;
}
}
int cnt = 0;
for(int i=0; i<N; ++i){
if(!brr[i]) cnt++;
}
printf("%d", cnt);
return 0;
}