2.1学习总结

2.1
动态规划
1.斐波那契数
2.爬楼梯
3.使用最小花费爬楼梯
4.不同路径1
5.不同路径2
6.数的拆分
7.分割等和子集
作业
1.Spreadsheets(字符串模拟)
2.Before an Exam(贪心)
3.放苹果(动态规划)
4.最大正方形(前缀和)
5.疯狂的采药(动态规划)
6.采药(动态规划)
7.搭配购买(并查集+动态规划)

动态规划:今天做了动态规划的基础题,和一些01背包,完全背包的问题

解动态规划的步骤:

1:明确dp数组的下标含义

2:明确递推公式,转移方程

3:初始化

题1:https://leetcode.cn/problems/fibonacci-number/description/
class Solution {
public:
    int fib(int n) {
        int f[32];
        memset(f,0,sizeof(f));
        f[1]=1,f[2]=1,f[3]=2;
        for (int i=4;i<=30;++i){
            f[i]=f[i-1]+f[i-2];
        }
        return f[n];
    }
};
题2:https://leetcode.cn/problems/climbing-stairs/description/

转移方程和斐波那契数列相同,初始化1,2台阶

dp[i]: 爬到第i层楼梯,有dp[i]种⽅法

class Solution {
public:
    int climbStairs(int n) {
        int dp[46];
        dp[1]=1,dp[2]=2,dp[3]=3;
        for (int i=4;i<=45;++i){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};
题3:https://leetcode.cn/problems/min-cost-climbing-stairs/description/

在爬楼梯的基础上加了花费,所以

dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int>dp(cost.size()+1);
        dp[0]=0,dp[1]=0;
        for (int i=2;i<=cost.size();++i){
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[cost.size()];
    }
};
题4:https://leetcode.cn/problems/unique-paths/description/

dp[i][j]的定义 :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

递推公式:由于一个可能从两个方向来所以dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

初始化:

for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
class Solution {
public:
    int uniquePaths(int m, int n) {
        int dp[105][105];
        for (int i=0;i<n;++i)dp[0][i]=1;
        for (int i=0;i<m;++i)dp[i][0]=1;
        for (int i=1;i<m;++i){
            for (int j=1;j<n;++j){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};
题5:https://leetcode.cn/problems/unique-paths-ii/

思路相同,但是要排除路障

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int dp[105][105];
        memset(dp,0,sizeof(dp));
        int m=obstacleGrid.size(),n=obstacleGrid[0].size();
        if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) return 0;
        for (int i=0;i<n && obstacleGrid[0][i]==0 ;++i) dp[0][i]=1;
        for (int i=0;i<m && obstacleGrid[i][0]==0 ;++i) dp[i][0]=1;
        for (int i=1;i<m;++i){
            for (int j=1;j<n;++j){
                if (obstacleGrid[i][j]==1)  continue;
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
                
            }
        }
        return dp[m-1][n-1];
    }
};
题6:https://leetcode.cn/problems/integer-break/description/

定义:dp[i]:分拆数字i,可以得到的最⼤乘积为dp[i]。

递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

class Solution {
public:
    int integerBreak(int n) {
        int dp[10000];
        memset(dp,0,sizeof(dp));
        dp[0]=1,dp[1]=1;
        for (int i=2;i<=n;++i){
            for (int j=1;j<=n;++j){
                if (i>j)
                    dp[i]=max(dp[i],max((i-j)*j,dp[i-j]*j));
            }
        }
        return dp[n];
    }
};
题7:https://leetcode.cn/problems/partition-equal-subset-sum/description/

01背包中,dp[j] 表示: 容量为j的背包,所背的物品价值最⼤可以为dp[j]。

放到本题中也是同理,这题主要是找是否出现sum/2的子集

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        vector<int>dp(10001,0);
        int len=nums.size();
        int sum=0;
        for (int i=0;i<len;++i)sum+=nums[i];
        if (sum%2==1)return false;
        int target=sum/2;
        for (int i=0;i<len;++i){
            for (int j=target;j>=nums[i];--j){
                dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
            }
        }
        if (dp[target]==target) return true;
        else return false;
    }
};

作业:

Spreadsheetshttps://www.luogu.com.cn/problem/CF1B

人们常用的电子表格软件(比如: Excel)采用如下所述的坐标系统:

第一列被标为 A,第二列为 B,以此类推,第 2626 列为 Z。接下来为由两个字母构成的列号: 第 2727 列为 AA,第 2828 列为 AB ⋯⋯ 在标为 ZZ 的列之后则由三个字母构成列号,如此类推。

行号为从 11 开始的整数。

单元格的坐标由列号和行号连接而成。比如,BC23 表示位于第 5555 列 2323 行的单元格。

有时也会采用被称为 RXCY 的坐标系统,其中 �X 与 �Y 为整数,坐标 (�(X,�)Y) 直接描述了对应单元格的位置。比如,R23C55 即为前面所述的单元格。

您的任务是编写一个程序,将所给的单元格坐标转换为另一种坐标系统下面的形式。

输入

第一行一个整数 �n (1(1 ≤≤ �n ≤≤ 105)105) 表示将会输入的坐标的数量。

接下来 �n 行,每行一个坐标。

注意: 每个坐标都是正确的。此外不会出现行号或列号大于 106106 的单元格。

输出 �n 行,每行一个被转换的坐标。

输出

�n 行,每行一个被转换的坐标。

输入输出样例

输入 #1复制

2
R23C55
BC23

输出 #1复制

BC23
R23C55

思路:重在模拟,就是有一个情况需要特判,当余数为0的时候,应该赋z

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
map<char,int>mp;
char s[101];
const char *Const=" ABCDEFGHIJKLMNOPQRSTUVWXYZ";
void f1(){
	int idx=0,col=0,row=0;
	while (isalpha(s[idx])){
		col=col*26+(s[idx]-'A'+1);
		idx++;
	}
	while (s[idx]){
		row=row*10+(s[idx]-'0');
		idx++;
	}
	cout<<"R"<<row<<"C"<<col<<endl;
}
void f2(){
	int idx=1,row=0;
	while (s[idx]!='C'){
		if (s[idx]>='0' && s[idx]<='9'){
			row=row*10+(s[idx]-'0');
		}
		idx++;
	}
	idx++;
	int col=0;
	while (s[idx]){
		col=col*10+(s[idx]-'0');
		idx++;
	}
	int t[101],j=0;
	for (;col;col=col/26-!(col%26)){
		if (col%26){
			t[++j]=col%26;
		}else t[++j]=26;
	}
	for (;j;--j)printf("%c",Const[t[j]]);
	cout<<row<<endl;
}
signed main(){
	int n;
	cin>>n;
	int flag;
	for (int i=0;i<n;++i){
		scanf("%s",&s);
		flag=0;
		for (int i=0;s[i] && !flag;++i){
			if (i && isdigit(s[i-1]) && isalpha(s[i])){
				flag=1;
			}	
		}
		if (flag)f2();
		else f1();
	}
}
Before an Examhttps://www.luogu.com.cn/problem/CF4B

明天皮特将要考生物。他并不很喜欢生物,但在 �d 天前他得知他将不得不参加此次考试。皮特严厉的父母勒令他立即复习,因此他在第 �i 天将需要学习不少于 ��������minTimei​ 小时,不多于 ��������maxTimei​ 小时。他们同时警告皮特:考试前一天,他将被检查他复习的完成情况。

因此,今天皮特的父母会要求他展示他考前复习的学习时间表。然而,他只记录这 �d 天以来他复习所用的总计用时 �������sumTime(小时).现在他希望知道他能否给他的父母展示一份时间表,包含 �d 个数,每个数 ��ℎ������schedulei​ 表示皮特第 �i 天在复习生物上的用时(单位为小时),并应满足上文提及的要求。

题目输入

第一行包含两个数:�,�������d,sumTime。

(1≤�≤30,0≤�������≤240)(1≤d≤30,0≤sumTime≤240),意义如上所述。

接下来 �d 行,每行两个数:��������,��������minTimei​,maxtimei​,两个数之间有一个空格,意义如上。(0≤��������≤��������≤8)(0≤minTimei​≤maxTimei​≤8)

题目输出

如果有解,在单独一行输出 YES,换行,输出任意一种满足上文要求的解。如果无解,在单独一行中输出 NO

Translated by @B_1168, @ivyjiao, @bye_wjx。

输入输出样例

输入 #1复制

1 48
5 7

输出 #1复制

NO

输入 #2复制

2 5
0 1
3 5

输出 #2复制

YES
1 4 

思路:用贪心的方法,先把最小时间的总和和最大时间的总和加起来,看提供的总时间是否在这个范围内,如果不在就不行,反之先假设每一天都是最小时间,然后从头到尾开始加时间,直到总时间为0

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
int d,sumtime;
int maxtime[31],mintime[31],ans[31];
signed main(){
	cin>>d>>sumtime;
	int maxs=0,mins=0;
	for (int i=0;i<d;++i){
		cin>>mintime[i];
		cin>>maxtime[i];
		maxs+=maxtime[i];
		mins+=mintime[i];
	}
	if (sumtime>=mins && sumtime <=maxs){
		cout<<"YES"<<endl; 
		sumtime-=mins;
		for (int i=0;i<d;++i){
			if (sumtime>maxtime[i]-mintime[i]){
				sumtime-=maxtime[i]-mintime[i];
				ans[i]=maxtime[i];
			}else{
				ans[i]=mintime[i]+sumtime;
				sumtime=0;
			}
		}
	}else {
		cout<<"NO";
		return 0;
	}
	for (int j=0;j<d;++j){
		cout<<ans[j]<<" ";
	}
}
放苹果https://www.luogu.com.cn/problem/P2386

题目描述

把 �m 个同样的苹果放在 �n 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法。(5,1,15,1,1 和 1,1,51,1,5 是同一种方法)

输入格式

第一行是测试数据的数目 �t,以下每行均包括二个整数 �m 和 �n,以空格分开。

输出格式

对输入的每组数据 �m 和 �n,用一行输出相应的结果。

输入输出样例

输入 #1复制

1
7 3

输出 #1复制

8

输入 #2复制

3
3 2
4 3
2 7

输出 #2复制

2
4
2

说明/提示

对于所有数据,保证:1≤�,�≤101≤m,n≤10,0≤�≤200≤t≤20。

思路:动态规划的方法,dp[i][j]表示i个苹果放在j个盘子里面的方案数

有以下几种情况:

当苹果数小于盘子数的时候,就那么方案数就相当于i个苹果放到i个盘子里面

当苹果数大于盘子数的时候,有两种选择:

第一可以选择每个盘子放一个苹果

f[i-j][j]

第二可以选择放弃一个盘子不放苹果

f[i][j-1]

所以就得到了递推公式

f[i][j]=f[i-j][j]+f[i][j-1]
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
signed main(){
	int t;
	cin>>t;
	int dp[25][25];
	while (t--){
		memset(dp,0,sizeof(dp));
		int a,b;
		cin>>a>>b;
		for (int i=1;i<=b;++i){
			dp[0][i]=1;
			dp[1][i]=1;
		}
		for (int i=1;i<=a;++i){
			dp[i][1]=1;
		}
		for (int i=2;i<=a;++i){
			for (int j=2;j<=b;++j){
				if (i<j)dp[i][j]=dp[i][i];
				else {
					dp[i][j]=dp[i][j-1]+dp[i-j][j];
				}
			}
		}
		cout<<dp[a][b]<<endl;
	}
}
最大正方形https://www.luogu.com.cn/problem/P1387

题目描述

在一个 �×�n×m 的只包含 00 和 11 的矩阵里找出一个不包含 00 的最大正方形,输出边长。

输入格式

输入文件第一行为两个整数 �,�(1≤�,�≤100)n,m(1≤n,m≤100),接下来 �n 行,每行 �m 个数字,用空格隔开,00 或 11。

输出格式

一个整数,最大正方形的边长。

输入输出样例

输入 #1复制

4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1

输出 #1复制

2

思路:前缀和加暴力搜索,边长从大到小遍历,每个点都走一遍直到遇到可以形成的正方形后退出

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
int f[105][105],a[105][105];
signed main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;++i){
		for (int j=1;j<=m;++j){
			cin>>f[i][j];
		}
	}
	for (int i=1;i<=n;++i){
		for (int j=1;j<=m;++j){
			a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+f[i][j];
		}
	}
	int b=min(m,n);
	for (int i=b;i>=1;--i){
		for (int j=1;j<=n-i+1;++j){
			for (int k=1;k<=m-i+1;++k){
				int j_j=j+i-1,k_k=k+i-1;
				int sum=a[j_j][k_k]-a[j-1][k_k]-a[j_j][k-1]+a[j-1][k-1];
				if (sum==i*i){
					cout<<i;
					return 0;
				}
			}
		}
	}
	cout<<-1;
}
疯狂的采药https://www.luogu.com.cn/problem/P1616

题目描述

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

11. 每种草药可以无限制地疯狂采摘。

22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式

输入第一行有两个整数,分别代表总共能够用来采药的时间 �t 和代表山洞里的草药的数目 �m。

第 22 到第 (�+1)(m+1) 行,每行两个整数,第 (�+1)(i+1) 行的整数 ��,��ai​,bi​ 分别表示采摘第 �i 种草药的时间和该草药的价值。

输出格式

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例

输入 #1复制

70 3
71 100
69 1
1 2

输出 #1复制

140

说明/提示

数据规模与约定
  • 对于 30%30% 的数据,保证 �≤103m≤103 。
  • 对于 100%100% 的数据,保证 1≤�≤1041≤m≤104,1≤�≤1071≤t≤107,且 1≤�×�≤1071≤m×t≤107,1≤��,��≤1041≤ai​,bi​≤104。

思路:完全背包问题,完全背包和01背包的区别就在于01背包不能拿之前拿过的物品,而完全背包可以无限次的拿

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
const int N=1e4+5;
const int M=1e7+5;
int tt[N],v[N];
int dp[M];
signed main(){
	int t,m;
	cin>>t>>m;
	for (int i = 1; i <= m; ++i) {
        cin>>tt[i]>>v[i];
    }
	memset(dp,0,sizeof(dp));
	for (int i=1;i<=m;++i){
		for (int j=tt[i];j<=t;++j){
			dp[j]=max(dp[j],dp[j-tt[i]]+v[i]);
		}
	}
	cout<<dp[t];
}
	
采药https://www.luogu.com.cn/problem/P1048

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 22 个整数 �T(1≤�≤10001≤T≤1000)和 �M(1≤�≤1001≤M≤100),用一个空格隔开,�T 代表总共能够用来采药的时间,�M 代表山洞里的草药的数目。

接下来的 �M 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

输入输出样例

输入 #1复制

70 3
71 100
69 1
1 2

输出 #1复制

3

说明/提示

【数据范围】

  • 对于 30%30% 的数据,�≤10M≤10;
  • 对于全部的数据,�≤100M≤100。

思路:01背包问题

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
const int N=1e4+5;
const int M=1e7+5;
int tt[105],v[105];
int dp[1005][1005];
signed main(){
	int t,m;
	cin>>t>>m;
	for (int i = 1; i <= m; ++i) {
        cin>>tt[i]>>v[i];
    }
	memset(dp,0,sizeof(dp));
	for (int i=1;i<=m;++i){
		for (int j=1;j<=t;++j){
			if (j>=tt[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-tt[i]]+v[i]);
			else dp[i][j]=dp[i-1][j];
		}
	}
	cout<<dp[m][t];
}
	
搭配购买https://www.luogu.com.cn/problem/P1455

明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 �n 朵云,云朵已经被老板编号为 1,2,3,...,�1,2,3,...,n,并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。

输入格式

第一行输入三个整数,�,�,�n,m,w,表示有 �n 朵云,�m 个搭配和你现有的钱的数目。

第二行至 �+1n+1 行,每行有两个整数, ��,��ci​,di​,表示第 �i 朵云的价钱和价值。

第 �+2n+2 至 �+1+�n+1+m 行 ,每行有两个整数 ��,��ui​,vi​。表示买第 ��ui​ 朵云就必须买第 ��vi​ 朵云,同理,如果买第 ��vi​ 朵就必须买第 ��ui​ 朵。

输出格式

一行,表示可以获得的最大价值。

输入输出样例

输入 #1复制

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

输出 #1复制

1

说明/提示

  • 对于 30%30% 的数据,满足 1≤�≤1001≤n≤100;
  • 对于 50%50% 的数据,满足 1≤�,�≤1031≤n,w≤103,1≤�≤1001≤m≤100;
  • 对于 100%100% 的数据,满足 1≤�,�≤1041≤n,w≤104,0≤�≤5×1030≤m≤5×103。

思路:由于云之间有关系,所以先用并查集连起来,然后再用动态规划,找到在有限的钱的范围内的最大价值

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
#define int long long 
int s[10005],v[10005],dp[10005];
int f[10005];
int find(int x){
	if (f[x]==x)return x;
	else {
		f[x]=find(f[x]);
		return f[x];
	}
}
void unionn(int i,int j){
	f[find(i)]=find(j);
}
signed main(){
	int n,m,w;
	cin>>n>>m>>w;
	for (int i=1;i<=n;++i){
		cin>>s[i]>>v[i];
		f[i]=i;
	}
	for (int i=1;i<=m;++i){
		int a,b;
		cin>>a>>b;
		unionn(a,b);
	}
	for (int i=1;i<=n;++i){
		if (find(i)!=i){
			s[find(i)]+=s[i];
			s[i]=0;
			v[find(i)]+=v[i];
			v[i]=0;
		}
	}
	for (int i=1;i<=n;++i){
		for (int j=w;j>=s[i];--j){
			dp[j]=max(dp[j],dp[j-s[i]]+v[i]);
		}
	}
	cout<<dp[w];
}
  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值