【动态规划】

目录

知识框架

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

题目描述:
在这里插入图片描述

题目思路:

  1. 一个数能被3整除,说明这个数每位的和是3的倍数
  2. 利用动态规划的思想,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;
 } 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值