【九十五】【算法分析与设计】22级一二班 蛮力法实验,字符串编码与解码,【模板】01背包, wnm的全排列,非递归实现组合型枚举,内置函数找全排列--->找固定数量的组合

字符串编码与解码

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

题目描述

给你两个长度相同的字符串a,b,现在已知b是a编码之后的结果

比如a = "CAT", b = "DOG", 那么D其实是C,O其实是A,G其实是T

现在给你一个字符串c,如果c能够被解码出来,输出c解码后的字符串,如果不能输出@

输入描述:

输入三行,每行一个字符串,长度在50以内

输出描述:

输出一个字符串

示例1

输入

复制CAT DOG GOD

CAT

DOG

GOD

输出

复制TAC

TAC

示例2

输入

复制BANANA METETE TEMP

BANANA

METETE

TEMP

输出

复制@

@

示例3

输入

复制THEQUICKBROWNFOXJUMPSOVERTHELAZYHOG UIFRVJDLCSPXOGPYKVNQTPWFSUIFMBAZIPH DIDYOUNOTICESKIPPEDLETTER

THEQUICKBROWNFOXJUMPSOVERTHELAZYHOG

UIFRVJDLCSPXOGPYKVNQTPWFSUIFMBAZIPH

DIDYOUNOTICESKIPPEDLETTER

输出

复制CHCXNTMNSHBDRJHOODCKDSSDQ

CHCXNTMNSHBDRJHOODCKDSSDQ

说明

E之外的其他字母一一对应之后,E只能对应D

备注:

子任务1:|a| <= 10

子任务2:|a| <= 40

子任务3:无限制

1.

题目意思给出三个字符串分别是a,b,c.

a字符串通过一些编码规则转化为b,也就是b通过一些解码规则解码成a.

现在给我们c字符串,希望将c字符串种所有的字母全部解码,如果能够全部解码就输出解码之后的字符串,如果不能全部解码就输出@.

2.

注意看示例三,示例三告诉我们解码规则并没有包含所有的字母,他只包含了25个字母,但是这25个字母一一对应,因此最后一个字母只能匹配一个字母,所以这种情况需要补充一个解码规则.

3.

解题思路,遍历a,b字符串,收集所有的解码规则放到hash表中,这样我们可以快速查询到某一个字母解码之后的字母是什么,然后遍历c字符串,如果有一个字母找不到解码规则,就输出@,否则全部解码然后输出.

4.

如何快速找到25个字母解码规则一一对应,需要补充一个解码规则的情况,只需要用count1和count2记录解码规则使用过的字母和映射过去的字母使用情况即可.

如果两者都等于25说明此时需要补充解码规则.

用index1和index2记录没有使用过的字母,这样就可以快速找到需要补充的解码规则.

 
#include<bits/stdc++.h>  // 引入标准库中的所有头文件
using namespace std;
#define int long long  // 定义int为long long类型,方便处理大数
#define endl '\n'  // 定义endl为换行符

string a, b, c;  // 声明三个字符串a, b, c
map<char, char> jiema;  // 定义一个映射表,用于存储解码规则
vector<bool> visited1;  // 用于记录b中出现的字母
int count1 = 0;  // 记录b中不同字母的数量
int index1 = -1;  // 记录b中未出现字母的索引
vector<bool> visited2;  // 用于记录a中出现的字母
int count2 = 0;  // 记录a中不同字母的数量
int index2 = -1;  // 记录a中未出现字母的索引
string ret;  // 用于存储解码后的结果字符串

void init() {
    visited1.assign(26, false);  // 初始化visited1向量,大小为26,所有值为false
    visited2.assign(26, false);  // 初始化visited2向量,大小为26,所有值为false

    for (int i = 0; i < a.size(); i++) {  // 遍历字符串a和b的每个字符
        jiema[b[i]] = a[i];  // 将b的字符映射到a的字符,存入jiema映射表
        visited1[b[i] - 'A'] = true;  // 标记b中的字符
        visited2[a[i] - 'A'] = true;  // 标记a中的字符
    }

    for (int i = 0; i < 26; i++) {  // 遍历26个字母
        if (visited1[i] == true) {
            count1++;  // 统计b中出现的不同字母数量
        } else {
            index1 = i;  // 记录b中未出现的字母的索引
        }
        if (visited2[i] == true) {
            count2++;  // 统计a中出现的不同字母数量
        } else {
            index2 = i;  // 记录a中未出现的字母的索引
        }
    }
    if (count1 == count2 && count1 == 25) {  // 如果b和a中都有25个不同字母
        jiema[index1 + 'A'] = index2 + 'A';  // 将最后一个未使用的字母进行映射
    }
}

void solve() {
    for (int i = 0; i < c.size(); i++) {  // 遍历字符串c的每个字符
        if (jiema.count(c[i])) {  // 如果c中的字符在jiema映射表中
            ret.push_back(jiema[c[i]]);  // 将对应的解码字符加入结果字符串
        } else {
            cout << "@" << endl;  // 如果有字符不能解码,输出@并返回
            return;
        }
    }
    cout << ret << endl;  // 输出解码后的结果字符串
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  // 加速输入输出

    cin >> a >> b >> c;  // 输入三个字符串a, b, c
    init();  // 初始化映射表
    solve();  // 解码字符串c并输出结果

    return 0;  // 返回0,表示程序执行成功
}

【模板】01背包

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

题目描述

你有一个背包,最多能容纳的体积是V。

现在有n个物品,第i个物品的体积为viv_ivi ,价值为wiw_iwi。

(1)求这个背包至多能装多大价值的物品?

(2)若背包恰好装满,求至多能装多大价值的物品?

输入描述:

第一行两个整数n和V,表示物品个数和背包体积。

接下来n行,每行两个数viv_ivi和wiw_iwi,表示第i个物品的体积和价值。

1≤n,V,vi,wi≤10001 \le n,V,v_i,w_i \le 10001≤n,V,vi,wi≤1000

输出描述:

输出有两行,第一行输出第一问的答案,第二行输出第二问的答案,如果无解请输出0。

示例1

输入

复制3 5 2 10 4 5 1 4

3 5

2 10

4 5

1 4

输出

复制14 9

14

9

说明

装第一个和第三个物品时总价值最大,但是装第二个和第三个物品可以使得背包恰好装满且总价值最大。

示例2

输入

复制3 8 12 6 11 8 6 8

3 8

12 6

11 8

6 8

输出

复制8 0

8

0

说明

装第三个物品时总价值最大但是不满,装满背包无解。

备注:

要求O(nV)的时间复杂度,O(V)空间复杂度

二进制暴力枚举(超时)

1.

0/1背包组合问题.一共有2^n种方案,对于每一个物品可以选择或者不选择,一共有n个物品,对于第一个物品可以选择或者不选择,两种方案,对于第二个物品可以选择或者不选择,两种方案,........2*2*2....

一共的方案数是2^n种.

2.

在每一个方案中,对于每一个物品都可以选择或者不选择,可以用1表示选择,用0表示不不选择,所以需要n个位置填上1或者0表示每一个物品的使用情况.如果这n个位置是二进制的形式,那么000..00~111..11一共是2^n个,最大值是2^n-1.

所以只需要遍历0~2^n-1,对于每一个数的二进制数就是一种方案,然后对于每一种方案找到对应的选择的物品即可.

 
#include<bits/stdc++.h>  // 引入所有标准库头文件
using namespace std;
#define int long long  // 将int定义为long long类型,方便处理大数
#define endl '\n'  // 定义endl为换行符

int n, v;  // 物品数量和背包容量
struct node {
    int weight = 0;  // 物品的重量
    int value = 0;  // 物品的价值
};
vector<node> wupin;  // 存储所有物品的向量
int ret1 = 0;  // 第一问的答案,背包最多能装的最大价值
int ret2 = 0;  // 第二问的答案,背包恰好装满时的最大价值
int ncount = 0;  // 方案总数

void init() {
    ncount = pow(2, n);  // 计算2^n,总方案数
}

void solve() {
    for (int i = 0; i < ncount; i++) {  // 遍历所有方案
        node temp;  // 临时节点存储当前方案的总重量和总价值
        for (int j = 0; j < n; j++) {  // 遍历每个物品
            if (temp.weight > v) break;  // 如果当前方案的重量超过背包容量,退出
            if (i & (1 << j)) {  // 判断方案i的第j位是否为1,即是否选择该物品
                temp.value += wupin[j].value;  // 加上该物品的价值
                temp.weight += wupin[j].weight;  // 加上该物品的重量
            }
        }
        if (temp.weight <= v)  // 如果当前方案的重量不超过背包容量
            if (ret1 < temp.value) ret1 = temp.value;  // 更新第一问的答案

        if (temp.weight == v)  // 如果当前方案的重量恰好等于背包容量
            if (ret2 < temp.value) ret2 = temp.value;  // 更新第二问的答案
    }
    cout << ret1 << endl << ret2 << endl;  // 输出答案
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  // 加速输入输出

    cin >> n >> v;  // 输入物品数量和背包容量
    wupin.assign(n, {0, 0});  // 初始化物品向量
    for (int i = 0; i < n; i++) {
        cin >> wupin[i].weight >> wupin[i].value;  // 输入每个物品的重量和价值
    }
    init();  // 初始化方案总数
    solve();  // 解决问题并输出答案
}

自顶向下的动态规划,记忆化递归(超时)

 
#include<bits/stdc++.h>  // 引入所有标准库头文件
using namespace std;
#define int long long  // 将int定义为long long类型,方便处理大数
#define endl '\n'  // 定义endl为换行符

struct node {
    int weight;  // 物品的重量
    int value;  // 物品的价值
};
int n, v;  // 物品数量和背包容量
vector<node> wupin;  // 存储所有物品的向量
vector<vector<int>> dp;  // 动态规划数组

// 定义一个递归函数,解决01背包问题的第一问
int dfs(int i, int weight) {
    if (weight < 0) return LLONG_MIN;  // 如果重量小于0,返回负无穷表示不可行
    if (weight == 0) return 0;  // 如果重量正好为0,返回0价值
    if (i < 0) return 0;  // 如果没有物品可选,返回0价值
    if (dp[i][weight] != -1) return dp[i][weight];  // 如果dp值已经计算过,直接返回
    dp[i][weight] = max(dfs(i - 1, weight), wupin[i].value + dfs(i - 1, weight - wupin[i].weight));  // 选择不放和放当前物品的最大值
    return dp[i][weight];
}

// 定义另一个递归函数,解决01背包问题的第二问
int dfs1(int i, int weight) {
    if (weight < 0) return LLONG_MIN;  // 如果重量小于0,返回负无穷表示不可行
    if (weight == 0) return 0;  // 如果重量正好为0,返回0价值
    if (i < 0) return LLONG_MIN;  // 如果没有物品可选,返回负无穷表示不可行
    if (dp[i][weight] >= 0) return dp[i][weight];  // 如果dp值已经计算过,直接返回
    dp[i][weight] = max(dfs1(i - 1, weight), wupin[i].value + dfs1(i - 1, weight - wupin[i].weight));  // 选择不放和放当前物品的最大值
    return dp[i][weight];
}

// 初始化dp数组
void init() {
    dp = vector<vector<int>>(n, vector<int>(v + 1, -1));  // 初始化为(n, v+1)的二维数组,值为-1
}

void solve() {
    for (int i = 0; i < n; i++)  // 遍历每个物品
        for (int j = 0; j <= v; j++) {  // 遍历每个可能的重量
            dfs(i, j);  // 计算dfs
            dfs1(i, j);  // 计算dfs1
        }

    cout << dfs(n - 1, v) << endl;  // 输出第一问的答案
    init();  // 重新初始化dp数组
    cout << (dfs1(n - 1, v) >= 0 ? dfs(n - 1, v) : 0) << endl;  // 输出第二问的答案,如果没有可行解输出0
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  // 加速输入输出

    cin >> n >> v;  // 输入物品数量和背包容量
    wupin.assign(n, {0, 0});  // 初始化物品向量
    for (int i = 0; i < n; i++) {
        cin >> wupin[i].weight >> wupin[i].value;  // 输入每个物品的重量和价值
    }

    init();  // 初始化dp数组
    solve();  // 解决问题并输出答案
}

自底向上的动态规划

1.

状态转移方程根据递归的思维找到.

需要注意的就是填表顺序,填表顺序根据状态转移方程判断.

dp[i][j] = max(dp[i - 1][j], wupin[i].value + dp[i - 1][j - wupin[i].weight]);

这是状态转移方程,当我们填写(i,j)位置的dp值,需要用到(i-1,j)和(i-1,j - wupin[i].weight])位置的dp值.

所以i一定从小到大,j也一定从小到大.

2.

边界情况用if条件判断一下,防止越界的情况.

对于dp[i][j]表示从i下标之前的物品进行选择,最多花费j体积获得的最大价值.这种情况,第一个考虑的越界情况是i==0的时候,此时只有一个物品,只需要判断此时j体积和物品体积比较一下即可.

接下来判断的是j - wupin[i].weight]越界的情况.

3.

dp[i][j]表示从i下标之前的物品进行选择,刚好花费j体积获得的最大价值.此时的边界处理多了一些操作.

用-1表示的是无法获取价值,如果无法获取价值就不考虑这种情况.

 
#include <bits/stdc++.h>  // 引入所有标准库头文件
using namespace std;
#define int long long  // 将int定义为long long类型,方便处理大数
#define endl '\n'  // 定义endl为换行符

struct node {
    int value = 0;  // 物品的价值
    int weight = 0;  // 物品的重量
};
int n = 0, v = 0;  // 物品数量和背包容量
vector<node> wupin;  // 存储所有物品的向量
vector<vector<int>> dp;  // 动态规划数组

void init() {
    dp.assign(n, vector<int>(v + 1, 0));  // 初始化dp数组,大小为n*(v+1),初始值为0
}

void solve() {
    // dp[i][j]表示从i下标之前的物品进行选择,最多花费j体积获得的最大价值
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= v; j++) {
            if (i == 0) {  // 边界情况:只有一个物品
                dp[i][j] = (j >= wupin[i].weight) ? wupin[i].value : 0;  // 判断是否能放下物品,能放下则取物品价值,否则为0
            } else if (j < wupin[i].weight) {  // 当前背包容量不足以放下当前物品
                dp[i][j] = dp[i - 1][j];  // 继承前一个状态的值
            } else {
                dp[i][j] = max(dp[i - 1][j], wupin[i].value + dp[i - 1][j - wupin[i].weight]);  // 取不放当前物品和放当前物品两种情况的最大值
            }
        }
    }
    cout << dp[n - 1][v] << endl;  // 输出第一问的答案:背包最多能装的最大价值

    init();  // 重新初始化dp数组
    // dp[i][j]表示从i下标之前的物品进行选择,刚好花费j体积获得的最大价值
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= v; j++) {
            if (i == 0) {  // 边界情况:只有一个物品
                dp[i][j] = (j == wupin[i].weight) ? wupin[i].value : -1;  // 判断是否恰好能放下物品,能放下则取物品价值,否则为-1
                if (j == 0) dp[i][j] = 0;  // 如果重量为0,价值也为0
            } else if (j < wupin[i].weight) {  // 当前背包容量不足以放下当前物品
                dp[i][j] = dp[i - 1][j];  // 继承前一个状态的值
            } else {
                if (dp[i - 1][j - wupin[i].weight] == -1) {  // 如果剩余容量无法放下物品
                    dp[i][j] = dp[i - 1][j];  // 继承前一个状态的值
                } else {
                    dp[i][j] = max(dp[i - 1][j], wupin[i].value + dp[i - 1][j - wupin[i].weight]);  // 取不放当前物品和放当前物品两种情况的最大值
                }
            }
        }
    }

    cout << (dp[n - 1][v] >= 0 ? dp[n - 1][v] : 0) << endl;  // 输出第二问的答案:背包恰好装满时的最大价值,如果无解输出0
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  // 加速输入输出

    cin >> n >> v;  // 输入物品数量和背包容量
    wupin.assign(n, {0, 0});  // 初始化物品向量
    for (int i = 0; i < n; i++) 
        cin >> wupin[i].weight >> wupin[i].value;  // 输入每个物品的重量和价值
    
    init();  // 初始化dp数组
    solve();  // 解决问题并输出答案
}

wnm的全排列

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

题目描述

wnm最近迷上了全排列,简单的输出一个字符串所有的全排列已经难不住他了。最近他遇到了一个新问题,一个仅由0-9组成的字符串,对于它所有排列所构成的数字按升序排列后的序列,第n个数是多少呢?

输入描述:

首先输入一个正整数t(1<=t<=1000),表示共有t组测试数据。

对于每组测试数据,每行包含一个仅由0-9组成的字符串s(1<=|s|<=6,保证每个字符只会出现一次)和一个正整数n(1<=n<=1000)。

输出描述:

请你找到这个字符串所有的排列所构成的数字按升序排列后的第n个数字,如果存在就输出这个数字,否则输出"no solution"。

示例1

输入

复制3 120 1 321 2 213 10

3

120 1

321 2

213 10

输出

复制Case 1: 12 Case 2: 132 Case 3: no solution

Case 1: 12

Case 2: 132

Case 3: no solution

说明

对于样例1:120所有排列所构成的数字按升序排序后的序列为12,21,102,120,201,210,所以第1个数字是12。

对于样例2:321所有排列所构成的数字按升序排序后的序列为123,132,213,231,312,321,所以第2个数字是132。

对于样例3:213所有排列所构成的数字按升序排序后的序列同样例2,不存在第10个数字。

1.

全排列问题,用内置函数next_permutation找到所有的全排列情况.

next_permutation(arr.begin(),arr.end()).使得arr变成下一个全排列的形式.

也就是将所有的全排列按照某一个逻辑排序了,next_permutation只是找到下一个全排列的序列形式.

人为认为第一个全排列的序列是默认sort排序后的序列.

所以使用之前需要将序列sort排序一下.

2.

解题思路就是找到所有的全排列序列,然后放到一个ret数组中,然后将ret数组排序直接返回第n个输出即可.

 
#include <bits/stdc++.h>  // 引入所有标准库头文件
using namespace std;
#define int long long  // 将int定义为long long类型,方便处理大数
#define endl '\n'  // 定义endl为换行符

int t;  // 测试数据组数
string s;  // 输入的仅由0-9组成的字符串
int n;  // 要求的第n个全排列数字

vector<string> sortstr;  // 存储所有全排列的向量

void init() {
    sortstr.clear();  // 清空sortstr向量
}

void solve() {
    sort(s.begin(), s.end());  // 将字符串按升序排序
    do {
        sortstr.push_back(s);  // 将当前排列加入sortstr向量
    } while (next_permutation(s.begin(), s.end()));  // 生成下一个排列,直到没有更多排列
    sort(sortstr.begin(), sortstr.end());  // 对所有排列再进行一次排序,确保按升序排列
    if (n <= sortstr.size()) {  // 如果n不超过排列的数量
        cout << stoll(sortstr[n - 1]) << endl;  // 输出第n个排列
    } else {
        cout << "no solution" << endl;  // 否则输出“no solution”
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  // 加速输入输出
    cin >> t;  // 输入测试数据组数
    int count = 1;  // 记录测试数据的编号
    while (t--) {
        cin >> s >> n;  // 输入字符串和要求的第n个排列
        cout << "Case " << count++ << ": ";  // 输出测试数据的编号
        init();  // 初始化,清空sortstr
        solve();  // 解决当前测试数据
    }
    return 0;  // 返回0,表示程序执行成功
}

非递归实现组合型枚举

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

题目描述

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。n>0n \gt 0n>0, 0≤m≤n0 \leq m \leq n0≤m≤n, n+(n−m)≤25n+(n-m)\leq 25n+(n−m)≤25。

输入描述:

两个整数n,m。

输出描述:

按照从小到大的顺序输出所有方案每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 9 12排在1 3 10 11前面)。

示例1

输入

复制5 3

5 3

输出

复制1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5

1 2 3

1 2 4

1 2 5

1 3 4

1 3 5

1 4 5

2 3 4

2 3 5

2 4 5

3 4 5

1.

组合型枚举,固定n和m,因此可以用全排列的方式去找所有的组合形式.

思路是用一个vector<bool>开辟n个空间,分别对应n个物品,其中m个位置为true.表示这m个物品选择.

然后找到所有的全排列形式,此时就找到了所有的组合序列.

 
#include<bits/stdc++.h>  // 引入所有标准库头文件
using namespace std;
#define int long long  // 将int定义为long long类型,方便处理大数
#define endl '\n'  // 定义endl为换行符

int n, m;  // 物品数量n和选择数量m
vector<bool> findnums;  // 存储是否选择某个物品的布尔向量
vector<vector<int>> ret;  // 存储所有组合的结果

void init() {
    findnums.assign(n, false);  // 初始化findnums向量,大小为n,所有值为false
    for(int i = 0; i < m; i++) {  // 将前m个位置设置为true,表示选择前m个物品
        findnums[i] = true;
    }
    sort(findnums.begin(), findnums.end());  // 将findnums向量按升序排序
}

void solve() {
    do {
        vector<int> temp;  // 临时向量,存储当前组合
        for(int i = 0; i < n; i++) {
            if(findnums[i]) {  // 如果findnums[i]为true,表示选择该物品
                temp.push_back(i + 1);  // 将物品编号(从1开始)加入临时向量
            }
        }
        ret.push_back(temp);  // 将当前组合加入结果向量
    } while(next_permutation(findnums.begin(), findnums.end()));  // 生成下一个排列,直到没有更多排列
    
    sort(ret.begin(), ret.end());  // 对所有组合结果进行排序
    for(const auto& vec : ret) {  // 遍历每个组合
        for(const auto& nums : vec) {  // 遍历组合中的每个数字
            cout << nums << " ";  // 输出数字
        }
        cout << endl;  // 输出换行符
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  // 加速输入输出
    cin >> n >> m;  // 输入物品数量n和选择数量m
    
    init();  // 初始化findnums向量
    solve();  // 生成并输出所有组合结果
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值