首页 > 编程语言 >C++算法学习心得八.动态规划算法(3)

C++算法学习心得八.动态规划算法(3)

时间:2024-03-16 14:58:06浏览次数:18  
标签:遍历 target int sum 学习心得 C++ 算法 背包 dp

1.最后一块石头的重量II(1049题)

题目描述:

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;

如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:

  • 输入:[2,7,4,1,8,1]
  • 输出:1

解释:

  • 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
  • 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
  • 组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
  • 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值

dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。 

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。

而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了。

当然也可以把石头遍历一遍,计算出石头总重量 然后除2,得到dp数组的大小。

我这里就直接用15000了。

接下来就是如何初始化dp[j]呢,因为重量都不会是负数,所以dp[j]都初始化为0就可以了,这样在递归公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);中dp[j]才不会初始值所覆盖。

如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!

那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。

在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的

那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;//计算总和
        vector<int>dp(15001,0);//定义初始化dp数组
        //求石头总和
        for(int i = 0;i < stones.size();i++){
            sum += stones[i];
        }
        //目标和为其一半
        int target = sum / 2;
        //遍历顺序,遍历物品,
        for(int i = 0;i < stones.size();i++){
            //遍历背包,从后向前遍历
            for(int j = target;j >= stones[i];j--){
                dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);//递推公式,实现
            }
        }
        return sum - dp[target] - dp[target];//目标最靠近的值,两个差就是所求
    }
};
  • 时间复杂度:O(m × n) , m是石头总重量(准确的说是总重量的一半),n为石头块数
  • 空间复杂度:O(m)

2. 目标和(494题)

题目描述:

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

  • 输入:nums: [1, 1, 1, 1, 1], S: 3
  • 输出:5

解释:

  • -1+1+1+1+1 = 3
  • +1-1+1+1+1 = 3
  • +1+1-1+1+1 = 3
  • +1+1+1-1+1 = 3
  • +1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

既然为target,那么就一定有 left组合 - right组合 = target。

left + right = sum,而sum是固定的。right = sum - left

公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。

target是固定的,sum是固定的,left就可以求出来

回溯算法:

class Solution {
private:
    vector<vector<int>>result;//结果集
    vector<int>path;//路径
    //定义回溯函数,转变为组合问题,startindex,
    void backtracking(vector<int>& candicate,int target,int sum,int startindex){
        //终止条件
        if(sum == target){
            result.push_back(path);
        }
        //遍历,从startindex开始
        for(int i = startindex;i < candicate.size() && sum + candicate[i] <= target;i++){
            sum += candicate[i];//递归
            path.push_back(candicate[i]);//加入路径
            backtracking(candicate,target,sum,i+1);//递归函数
            sum -= candicate[i];//回溯
            path.pop_back();//回溯
        }
    }
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;//总和
        for(int i = 0;i < nums.size();i++)sum += nums[i];//计算总和
        if(target > sum)return 0;//如果目标值大于总和,返回0
        if((target+sum)%2)return 0;//如果相除除不开,返回0
        int bagSize = (target + sum) / 2;
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end());//需要排序
        backtracking(nums, bagSize, 0, 0);
        return result.size();
    }
};

动态规划:

转化为01背包问题

假设加法的总和为x,那么减法对应的总和就是sum - x。

所以我们要求的是 x - (sum - x) = target

x = (target + sum) / 2

此时问题就转化为,装满容量为x的背包,有几种方法

dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法

只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。

dp[j] += dp[j - nums[i]]

背包解决排列组合问题 

dp[0] 一定要初始化为1

01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;//总和
        for(int i = 0;i < nums.size();i++)sum += nums[i];//求总和
        if (abs(target) > sum) return 0; // 此时没有方案
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        int bagsize = (target+sum)/2;//求包大小
        vector<int>dp(bagsize+1,0);//定义dp数组,且初始化
        dp[0] = 1;//给dp[0]定义为1
        //遍历外层遍历nums
        for(int i = 0;i < nums.size();i++){
            //内层循环是bag,,
            for(int j = bagsize;j >= nums[i];j--){
                dp[j] += dp[j-nums[i]];//递推公式,主要求有多少种方法,组合问题
            }
        }
        return dp[bagsize];//返回
    }
};
  • 时间复杂度:O(n × m),n为正数个数,m为背包容量
  • 空间复杂度:O(m),m为背包容量

3.一和零(474题)

题目描述:

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例 1:

  • 输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3

  • 输出:4

  • 解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]

dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。

然后我们在遍历的过程中,取dp[i][j]的最大值。

所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

 因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

物品就是strs里的字符串,背包容量就是题目描述中的m和n

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>>dp(m+1,vector<int>(n+1,0));//定义dp数组,并且初始化为0
        //遍历字符串的每一个字符串
        for(string str : strs){
            //记录0和1的个数
            int onenum = 0,zeronum = 0;
            //记录每一个字符串的01个数
            for(char c : str){
                if(c == '0')zeronum++;
                else onenum++;
            }
             遍历背包容量且从后向前遍历
            for(int i = m;i >= zeronum;i--){
                for(int j = n;j >= onenum;j--){
                    dp[i][j] = max(dp[i][j],dp[i-zeronum][j-onenum] + 1);//递推公式,根据01背包来写的
                }
            }
        }
        return dp[m][n];
    }
};
  • 时间复杂度: O(kmn),k 为strs的长度
  • 空间复杂度: O(mn)

4.动态规划:完全背包理论基础 (卡码网第52题)

完全背包

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

01背包和完全背包唯一不同就是体现在遍历顺序上

01背包的核心代码:

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。

而完全背包的物品是可以添加多次的,所以要从小到大去遍历

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的 

#include <iostream>
#include <vector>
using namespace std;

// 先遍历背包,再遍历物品
void test_CompletePack(vector<int> weight, vector<int> value, int bagWeight) {

    vector<int> dp(bagWeight + 1, 0);

    for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
        for(int i = 0; i < weight.size(); i++) { // 遍历物品
            if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}
int main() {
    int N, V;
    cin >> N >> V;
    vector<int> weight;
    vector<int> value;
    for (int i = 0; i < N; i++) {
        int w;
        int v;
        cin >> w >> v;
        weight.push_back(w);
        value.push_back(v);
    }
    test_CompletePack(weight, value, V);
    return 0;
}

 5.零钱兑换II(518题)

题目描述:

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

  • 输入: amount = 5, coins = [1, 2, 5]
  • 输出: 4

解释: 有四种方式可以凑成总金额:

  • 5=5
  • 5=2+2+1
  • 5=2+1+1+1
  • 5=1+1+1+1+1

 纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!组合不强调元素之间的顺序,排列强调元素之间的顺序

dp[j]:凑成总金额j的货币组合数为dp[j]

dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。

所以递推公式:dp[j] += dp[j - coins[i]];

首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础

外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)

for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}

这种遍历顺序中dp[j]里计算的是组合数 

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}

dp[j]里算出来的就是排列数 

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int>dp(amount + 1,0);//设定dp数组含义,和对其其他非0元素初始化为0
        dp[0] = 1;//对0元素初始化为1
        //遍历顺序为外层物品,内层为背包,这个是需要组合数,相反则是排列数
        for(int i = 0; i < coins.size();i++){
            //注意遍历时,起始位置和终止位置边界
            for(int j = coins[i];j <= amount;j++){
                dp[j] += dp[j - coins[i]];//递推公式是01背包的推导
            }
        }
        return dp[amount];
    }
};
  • 时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度
  • 空间复杂度: O(m)

6.组合总和 Ⅳ(377题)

题目描述:

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

  • nums = [1, 2, 3]
  • target = 4

所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。

如果本题要把排列都列出来的话,只能使用回溯算法爆搜

dp[i]: 凑成目标正整数为i的排列个数为dp[i] 

求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。

个数可以不限使用,说明这是一个完全背包。

得到的集合是排列,说明需要考虑元素之间的顺序。

本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int>dp(target + 1,0);
        dp[0] = 1;
        for(int i = 0;i <= target;i++){// 遍历背包
            for(int j = 0;j < nums.size();j++){// 遍历物品
                if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
};
  • 时间复杂度: O(target * n),其中 n 为 nums 的长度
  • 空间复杂度: O(target)

7.爬楼梯(进阶版)(卡玛网57题)

题目描述:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

输入描述:输入共一行,包含两个正整数,分别表示n, m

输出描述:输出一个整数,表示爬到楼顶的方法数。

输入示例:3 2

输出示例:3

提示:

当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。

此时你有三种方法可以爬到楼顶。

  • 1 阶 + 1 阶 + 1 阶段
  • 1 阶 + 2 阶
  • 2 阶 + 1 阶

 dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法

递推公式为:dp[i] += dp[i - j]

递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。

将target放在外循环,将nums放在内循环。

每一步可以走多次,这是完全背包,内循环需要从前向后遍历

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, m;
    while (cin >> n >> m) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) { // 遍历背包
            for (int j = 1; j <= m; j++) { // 遍历物品
                if (i - j >= 0) dp[i] += dp[i - j];
            }
        }
        cout << dp[n] << endl;
    }
}
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(n)

总结:

最后一块石头的重量IIdp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j],选出任意两块石头,然后将它们一起粉碎,就是01背包问题,递推公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]),使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历,dp[j]都初始化为0,一堆石头的总重量是dp[target],另一堆就是sum - dp[target],最小石头重量就是(sum - dp[target]) - dp[target],

目标和:给定一组数,通过加减的方式达到目标和的方法,最后求达到这个方法的总数,target,那么就一定有 left组合 - right组合 = target,left + right = sum,而sum是固定的。right = sum - left,left - (sum - left) = target 推导出 left = (target + sum)/2,target是固定的,sum是固定的,left就可以求出来,回溯法可以解决,回溯法收集每个路径,所以需要两个数组,一个是路径,一个是结果集,组合问题,不要忘记startindex,定义终止条件,其实就是sum == target,如果得到了就将路径加入结果集,从startindex开始遍历,求sum值,然后加入路径,在之后我们需要进行递归,再进行弹出,减值回溯,这里我们需要进行排序,动态规划方法来实现,问题就转化为,装满容量为x的背包,有几种方法,dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法,就是混和组合问题,求组合的递推公式是dp[j] += dp[j - nums[i]],dp[0] 一定要初始化为1,01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。这里先求出sum,已知target所以可以求出背包容量,这里注意遍历顺序

一和零:一个二进制字符串,里面给定两个参数m,n,不超过这两个01的最多子集数,dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j],dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1,dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1),初始为0,物品就是strs里的字符串,背包容量就是题目描述中的m和n,我们这里注意定义二维数组的大小要M+1,n+1的二维数组,我们首先需要知道每个字符串的0和1的个数,这里注意再遍历背包和物品时候,边界m-onenum,n-zeronum,且倒序遍历,最后返回dp[m][n]即可

完全背包理论基础:有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。与01背包不同之处在于物品可以多次取用,这里需要掌握01背包核心代码,for(int i = 0; i < weight.size(); i++) { // 遍历物品,一维数组来实现,外层物品,内层背包,
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量  倒序
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}01背包从大到小遍历是因为保证只添加一次物品,
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量,这里顺序和01背包一样,只不过遍历顺序变了,注意边界
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的

零钱兑换II:给定一堆零钱,和一个总额,要求凑成总额的方法数量, 要求凑成总金额的物品组合个数,dp[j]:凑成总金额j的货币组合数为dp[j],递推公式:dp[j] += dp[j - coins[i]];dp[0]一定要为1,外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)

for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}这两种写法一样的概念

组合总和IV:给定一个数组,然后通过目标值,通过对组合来求出,顺序不同也可以作为不同的序列,求序列个数,所以这个是标准的排列问题,求排列个数,如果求排列组合的写法需要回溯法来实现,dp[i]: 凑成目标正整数为i的排列个数为dp[i] ,递推公式一般都是dp[i] += dp[i - nums[j]],dp[0]要初始化为1,如果求组合数就是外层for循环遍历物品,内层for遍历背包如果求排列数就是外层for遍历背包,内层for循环遍历物品。这里注意一下如果是排列的,需要加入判断条件,保证递推公式有意义

爬楼梯进阶版:正在爬楼梯。需要 n 阶你才能到达楼顶,每次可以爬至多m (1 <= m < n)个台阶。有多少种不同的方法可以爬到楼顶,dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法,递推公式为:dp[i] += dp[i - j],dp[0] 一定为1,先遍历背包再遍历物品注意条件设定和边界取值,注意不要让dp无意义,

标签:遍历,target,int,sum,学习心得,C++,算法,背包,dp
From: https://blog.csdn.net/weixin_55605689/article/details/136124857

相关文章

  • 代码随想录算法训练营day24 | leetcode 77. 组合
    目录题目链接:77.组合题目链接:77.组合题目描述:给定两个整数n和k,返回范围[1,n]中所有可能的k个数的组合。你可以按任何顺序返回答案。示例1:输入:n=4,k=2输出:[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],]示例2:输入:n=1,k=1输出:[[1]......
  • 南京邮电大学C++实验(一)类和对象的定义及使用(仅参考)
    实验名称:类和对象的定义及使用一、实验目的和要求(1)掌握类与对象的定义与使用方法,理解面向对象方法中通过对象间传递消息的工作机制。(2)正确掌握类的不同属性成员的使用方法。(3)掌握构造函数与析构函数的概念,理解构造函数与析构函数的执行过程。(4)掌握友元函数和友元类的定义......
  • 3.2_3 页面置换算法
    3.2_3页面置换算法  请求分页存储管理与基本分页存储管理的主要区别:  在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。  若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。  页面置换算......
  • C++模板的显式实例化
    C++模板前面讲到的模板的实例化是在调用函数或者创建对象时由编译器自动完成的,不需要程序员引导,因此称为隐式实例化。相对应的,我们也可以通过代码明确地告诉编译器需要针对哪个类型进行实例化,这称为显式实例化。编译器在实例化的过程中需要知道模板的所有细节:对于函数模板,也就是......
  • 将C++模板应用于多文件编程
    C++模板在将函数应用于多文件编程时,我们通常是将函数定义放在源文件(.cpp文件)中,将函数声明放在头文件(.h文件)中,使用函数时引入(#include命令)对应的头文件即可。编译是针对单个源文件的,只要有函数声明,编译器就能知道函数调用是否正确;而将函数调用和函数定义对应起来的过程,可以延迟到......
  • 邻接表存储带权的无向图(c++题解)
    题目描述给出一个无向带权图,顶点数为n,边数为m。输入格式第一行两个整数n,m,接下来有m行,每行3个整数u,v,w,表示点u到点v有一条边,边权为w。输出格式第i行输出第点i的所有邻接点,按照点i到该点的边权由小到大输出,如果边权相等,则按照点的编号有小到大输出。样例样例输入复......
  • 有向图的DFS(c++题解)
    题目描述给定一个有向图(不一定连通),有N个顶点,M条边,顶点从1..N依次编号,求出字典序最小的深度优先搜索顺序。输入格式第1行:2个整数,N(1≤N≤200)和M(2≤M≤5000)接下来M行,每行2个整数I,J,描述一条边从顶点I指向顶点J输出格式仅一行,一个顶点编号序列,表示字典序最小的深度优先搜索......
  • C++模板的实例化
    C++模板模板并不是真正的函数或类,它仅仅是编译器用来生成函数或类的一张“图纸”。模板不会占用内存,最终生成的函数或者类才会占用内存。由模板生成函数或类的过程叫做模板的实例化,相应地,针对某个类型生成的特定版本的函数或类叫做模板的一个实例。在学习模板以前,如果想针对不同......
  • C++模板中的非类型参数
    C++模板模板是一种泛型技术,目的是将数据的类型参数化,以增强C++语言(强类型语言)的灵活性。C++对模板的支持非常自由,模板中除了可以包含类型参数,还可以包含非类型参数,例如:template<typename T, int N> class Demo{ };template<class T, int N> void func(T (&arr)......
  • C++示例:学习C++标准库,std::unordered_map无序关联容器的使用
    01std::unordered_map介绍std::unordered_map是C++标准库中的一种无序关联容器模板类,它提供了一种将键映射到值的方法。它的底层基于哈希表实现,内容是无序的,可以在平均情况下在O(1)的时间复杂度内完成插入、查找和删除操作。值得注意的是,哈希表可能存在冲突,即不同的键值......