拜读我胡哥的精品复习资料 @acmack
胡哥发表重要讲话,强调算法的重要性,我等深受触动。
Map:底层是红黑树,按照key自动进行排序
list: 线性链表
我一直单纯的觉得list是列表,这不仅说明了胡哥与我的技术上的差距,还深刻的展现了胡哥与我在思想上的差距。
有几人你能够在学了一天后还能够写文章,我只能说胡哥牛逼。
胡哥就是技术上的“冬泳怪鸽”,你以为你胡哥沉寂了,不,他在等属于他的高光。
不知你有没有听说过acmack仅仅用了一个月的时间就将acm金奖捧回来,slowly,静待一个月,古月将拿下属于他的第一块金牌。当大家为古月大人欢呼时,他用食指抵住嘴唇,全场寂静,只听他说,火车是向前开的。
优先队列:最大的元素位于队首 ,最大的元素优先出队,同样,自动排序
也不一定是最大的元素在队首,可以进行修改,将最小的元素放在队头
就像说sort排序是不稳定一样,只需要简单修改,就能够将sort排序变成稳定的
分治步骤: 分解 解决 合并
在分解成小问题的时候就将小问题解决,最后再合并成原(大)问题
Fab数列用的递推,有水平
二分加了个左闭右开的例子,其实左闭右闭的区间我见过的比较少,我也不是很懂,算挖个坑。一般用的非左闭右闭(左闭右开/左开右闭)用的很多,甚至还有“男左女右”的口诀。
int BinarySearch(Type a[],const Type& x,int n)
{
int left=0;
int right=n;
while(left<right)//左闭右开
{
int middle=(left+right)>>1;
if (x==a[middle]) return middle;
if (x>a[middle]) left=middle+1; //middle已经判断不是了
else right=middle; //不-1 因为是左闭右开
}
return -1; //如果循环结束后仍然没有找到目标元素
}
太强了,胡直接讲明白了,我也明白了,威武!
动态规划:将问题分解若干个子问题,但这些子问题并不独立,它们犬牙参差,交相辉映,它们虽身处各地,但它们仍是一个整体,星星之火,可以燎原!
以自底向上的方式计算出最优值
性质: 最优子结构 重叠子问题
0-1背包:
解法一:
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1010;
int w[M],v[M];
int dp[M][M];
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++){ //两层循环都正序遍历 因为dp[i][j] 是由上面的元素和左上方得到的
dp[i][j]=dp[i-1][j];//表示不选择第i键物品
if (j>=v[i]){//当背包容量大于物品体积的时候取最大值
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
}
}
}
cout<<dp[n][m]<<endl;
return 0;
}
解法二:
#include<iostream>
#include<algorithm>
#include <cstdio>
using namespace std;
const int M=1010;
const int N=1e6+10;
int w[M],v[M];
int dp[M];
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=m;j>=v[i];j--){//倒序遍历不然会存在覆盖的问题
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[m]<<endl;
return 0;
}
第一解法,胡从数据表的角度解释,太精彩了
第二种解法,可能很多向我一样的蒟蒻可能不懂为什么倒序,你想啊,要是正序的话,你肯定会更新前面的数据,但前面的数据你不会再用,也就是说既没保证数据的稳定性,也没有充分的利用更新后的数据,所以你只能倒序,你妹的选啊,靓仔。而你倒序的话,能充分的比较在这个容量的所有的数据都比较了一遍,你才能得到性价比最高的选择
贪心:将原问题化成一个更小的与原问题具有相同形式的子问题
很多向我一样的蒟蒻不知道动态规划和贪心的区别,
贪心算法通常不会回溯,也不会重新考虑已经做出的选择。
动态规划则通过分解子问题、使用递归或迭代的方式自底向上求解子问题,保存子问题的解,并结合状态转移方程,逐步构建全局最优解。
解决问题的范围:
贪心算法:贪心算法通常适用于求解最小生成树、最短路径、区间调度等问题,对于某些问题无法得到全局最优解。
动态规划:动态规划算法可以应用于更广泛的问题,如背包问题、序列比对、图的最短路径等,能够求解更复杂的优化问题。
贪心不能求最优很难受,不要以为求出解就完了,有时候他求出来的解就很偏,虽然对,也能想明白,但和普通的逻辑不太一样,很怪。不过话又说回来,做题的话,只要解出来就好了。
选硬币:
现有面值分别为1角,5分,1分的硬币,请给出找1角5分钱的最佳方案。
#include <iostream>
#include <vector>
std::vector<int> findChange(int amount) {
std::vector<int> coins = {10, 5, 1}; // 按面值从大到小排序的硬币面值
std::vector<int> result(coins.size(), 0); // 用于存储每种硬币的数量
for (int i = 0; i < coins.size(); i++) {
int numCoins = amount / coins[i]; // 计算当前硬币面值的数量
result[i] = numCoins; // 存储数量
amount -= numCoins * coins[i]; // 更新剩余金额
}
return result;
}
int main() {
int amount = 15; // 需要找零的金额,单位为分
std::vector<int> change = findChange(amount);
std::cout << "找零方案为:" << std::endl;
std::cout << "1角1分硬币数量:" << change[0] << std::endl;
std::cout << "5分硬币数量:" << change[1] << std::endl;
std::cout << "1分硬币数量:" << change[2] << std::endl;
return 0;
}
胡哥把我们想的太菜了,这是很简单的事情,不需要多想。就是很简单的求整除数。
背包问题:
下面是贪心做法:
//形参n是物品的数量,c是背包的容量M,数组a是按物品的性价比降序排序
double knapsack(int n, bag a[], double c)
{
double cleft = c; //背包的剩余容量
int i = 0;//下标
double b = 0; //获得的价值
//当背包还能完全装入物品i
while(i <n && a[i].w<cleft) //这里的a[i]是一个结构体数组 元素包括重量、价值即(v,w,性价比)
{
cleft -= a[i].w;
b += a[i].v;
i++;
}
// 物品可拆分 a[i].v/a[i].w 是i物品的单位价值
if (i<n) b += 1.0*a[i].v*cleft/a[i].w;//凑满背包
return b;
}
背包问题贪心贪在,优先按照性价比降序排列,每次优先考虑价值最高的物品
胡哥总结的太好了,就像金子般闪闪发光,尤其是物品可拆分的情况,太香了
回溯:
回溯算法是一种通过递归的方式尝试所有可能的解空间的算法。其核心思想是通过不断地尝试所有的选择,当发现当前选择无法达到目标时,回溯到上一步进行其他选择,直到找到符合要求的解或遍历完所有可能的选择。
回溯算法通常适用于以下情况:
组合问题:需要从一组候选元素中找出所有可能的组合,如组合总和、子集、排列等问题。
搜索问题:需要在一个状态空间中找到满足特定约束条件的解,如图的遍历、八皇后问题等。
优化问题:需要找到满足特定条件下的最优解,如旅行商问题、背包问题等。
我还不太会,但我会学,相信我,one day 我将用自己的思想渗透这些伟大的定理公式
素数环问题:
素数环,从1到20这20个数摆成一个环,要求相邻的两个数的和是一个素数。
//判断质数
bool pd(int x,int y){
int k=2,i=x+y;
while (k<=sqrt(i)&&i%k!=0) k++; //
if (k>sqrt(i)) return true;//遍历半圈没有找到
else return false;//前面能被整除
}
void search(int t){
int i;
for (i=1;i<=20;i++)
if (pd(a[t-1],i)&&(!b[i])){ //判断当前数和前一位的和是不是素数同时 当前元素没有出现过
a[t]=i; //放入
b[i]=1; //出现一次
if (t==20) {
if (pd(a[20],a[1])) print(); }//注意边界 最后一个和第一个是连着的
else
search(t+1); //递归寻找下一个数字
b[i]=0;//回溯
}
}
int print(){
total++;
cout<<"<"<<total<<">";
for (int j=1;j<=20;j++)
cout<<a[j]<<" ";
cout<<endl;
}
search函数太优雅了,就是时间复杂度太高了,但不影响代码的思想,可惜了,受算力限制,没办法将所有的思想平等的对待,下课!