笔试编程算法题笔记(三 C++代码)

news2024/9/21 16:18:14

1.kotori和n皇后

 题意简单来说就是,在一个无穷大的棋盘上,不断插入k个皇后,皇后们如果在 同一行,同一列,在一个45°主对角线 在一个135°副对角线上,就可以互相攻击。

我们需要判断在第i个皇后插入后,是否存在互相攻击的情况。

解法:哈希表。

我们只需要将皇后的攻击范围存入哈希表(unordered_set)中,每次插入时判断这个皇后是否在这个攻击范围内即可。

因此我们需要创建四个哈希表。对应的行 列,两个对角线。

行和列很好理解,关于对角线

比如主对角线,如果y - x相等,说明在同一个主对角线上。同理,如果y + x相等,说明在同一个副对角线上。

最后,我们只需要记录下第一次出现皇后相互攻击的时机即可。

代码:

#include <iostream>
#include <unordered_set>

using namespace std;

int main()
{
    int k,t;
    cin >> k;
    int ret = 0x3f3f3f3f;
    unordered_set <int> row;
    unordered_set <int> low;
    unordered_set <int> dig1;// 主对角线
    unordered_set <int> dig2;// 副对角线
    
    int a,b;
    for(int i = 0; i < k; ++i)
    {
        cin >> a >> b;
        if(row.count(a) || low.count(b) || dig1.count(b - a) || dig2.count(b + a))
        {
            if(ret == 0x3f3f3f3f)
            {
                ret = i + 1;
            }
        }
        else 
        {
            row.insert(a);
            low.insert(b);
            dig1.insert(b - a);
            dig2.insert(b + a);
        }
    }
    
    cin >> t;
    int tmp;
    for(int i = 0; i < t; ++i)
    {
        cin >> tmp;
        if(tmp >= ret) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

 2.取金币

 这是一道比较难的区间dp问题。

题意简单,图中很容易理解。

状态表示:

dp[i][j]表示区间[i,j]的金币全部拿走,能获得的最大积分是多少。

状态转移方程:

假设我们取了[i,j]区间的第k堆金币,那么此时区间[i,j]能获得的最大积分也就是dp[i][j] = 

dp[i][k - 1] + dp[k + 1][j] + arr[k] * arr[i - 1] * arr[j + 1]。( i <= k <= j )

记得每次要取最大值。

然后我们再来考虑一下边界情况

首先是对于源数组的,我们可以给源数组的左右两边都新增一个元素1,这样可以在不影响计算的情况下防止数组越界。

然后对于dp表,我们可以多开两行和多开两列, 因为源数组我们新增了两个元素1,所以多开两行和两列可以避免填表时数组越界,dp表都初始化为0不影响计算结果。

并且我们的填表总是在对角线及以上填表的。因为 i >= j。

再来看填表顺序,跟以往不同,我们要对照着状态转移方程来看

dp[i][k - 1] + dp[k + 1][j] + arr[k] * arr[i - 1] * arr[j + 1]。( i <= k <= j )

我们发现填表需要用到j + 1,要用到i - 1,所以填表顺序为从下往上,从左往右填。 

最后返回dp[1,n]即可。

代码:

class Solution {
public:
    int arr[110];
    int dp[110][110];
    int getCoins(vector<int>& coins) {
        int n = coins.size();
        arr[0] = arr[n + 1] = 1;
        for(int i = 1; i <= n; ++i) arr[i] = coins[i - 1]; // 新增两个元素

        for(int i = n; i >= 1; --i)
        {
            for(int j = i; j <= n; j++)
            {
                for(int k = i; k <= j; ++k)
                    dp[i][j] = max(dp[i][j],dp[i][k - 1] + dp[k + 1][j] + arr[k] * arr[i - 1] * arr[j + 1]);
            }
        }

        return dp[1][n];
    }
};

3.四个选项

 题意简单来说就是有四个选项,给出四个选项的数量(保证四个选项加起来是12个),要将这些选项填到12个空里面,同时存在m个额外条件,每个条件使得第x个选项要等于第y个选项。

解法:递归 dfs

剪枝有两点:

1.如果当前该选项已经没有数量了,应该剪枝。

2.如果填入该选项发现不满足额外条件,应该剪枝。

细节:

1.可以用cnt[5]数组来存选项的数量,并用下标1 2 3 4 来映射选项的A B C D。

2.用哈希的思想来存额外条件,可以用一个二维的bool类型矩阵来存。

3.可以用一个变长数组来存选项填入的顺序,并事先加入一个占位符,方便对应下标映射关系。

代码:

#include <iostream>
#include <vector>

using namespace std;

int cnt[5];
int m,x,y;
bool same[13][13]; // 记录x,y相等的情况
int ret;
vector<int>path; // 存放路径

bool isSame(int pos,int cur)
{
    for(int i = 1; i < pos; ++i)
    {
        if(same[i][pos] && path[i] != cur) return false;
    }
    return true;
}

void dfs(int pos)
{
    if(pos > 12)
    {
        ret++;
        return;
    }
    
    for(int i = 1; i <= 4; ++i)
    {
        if(cnt[i] == 0) continue; // 剪枝1 该选项数量已为0
        if(!isSame(pos,i)) continue; // 剪枝2 选项不相同
        cnt[i]--;
        path.push_back(i);
        dfs(pos + 1);
        cnt[i]++;
        path.pop_back();
    }
}

int main()
{
    for(int i = 1; i <= 4; ++i) cin >> cnt[i];
    cin >> m;
    while(m--)
    {
        cin >> x >> y;
        same[x][y] = same[y][x] = true;
    }
    
    path.push_back(0); // 先增一个占位符
    dfs(1);
    
    cout << ret << endl;
    return 0;
}

4.接雨水

 本题解法很多,这里采用动态规划(预处理)的解法。

思想:先求出每一根柱子上能接多少雨水,然后把所有柱子能接的雨水累加即可。

那么怎么计算第i根柱子能接多少雨水呢?我们发现,第i根柱子能接的雨水根取决与该柱子左边柱子的最大值和右边柱子的最大值,取二者的较小值就是该柱子能接的雨水量。

所有我们可以用两个数组,left[i],right[i]来记录第i根柱子左边柱子的最大值,和右边柱子的最大值。

也就是前缀最大值。

另外有一个细节:

比如left[i],这个数组表示的是区间[0,i]的最大值,要把i也带进去,因为我们计算每一个柱子的公式是 ret += min(left[i],right[i]) - height[i];  如果没有把i算进去的话,恰好i柱子是最高的,那么结果就会产生负值,是不合理的,最少的雨水量是0。所以我们把i也算进去,才是合理的。

代码:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        vector<int> left(n);
        vector<int> right(n);
        left[0] = height[0];
        right[n - 1] = height[n - 1];
        for(int i = 1; i < n; ++i)
            left[i] = max(left[i - 1],height[i]);
        for(int i = n - 2; i >= 0; --i)
            right[i] = max(right[i + 1],height[i]);
        
        int ret = 0;
        for(int i = 0; i < n; ++i)
        {
            ret += min(left[i],right[i]) - height[i];
        }
        
        return ret;
    }
};

5.栈和排序

 

解法:栈+贪心

 贪心思想:优先让当前最大的值出栈。

先创建一个栈

1.依次进栈。

2. 先更新目标值

3.如果当前的栈顶元素大于目标值,说明它是当前能输出的最大值。

另外可以定义一个哈希表,来存元素是否入栈。

class Solution {
public:
    vector<int> solve(vector<int>& a) {
        int n = a.size();
        stack<int> s;
        int aim = n; // 目标值
        vector<int> ret;
        vector<bool> hash(n + 1); 
        
        for(auto x : a)
        {
            s.push(x);
            hash[x] = true;

            while(hash[aim]) // 先更新目标值
            {
                aim--;
            }

            while(s.size() && s.top() >= aim)
            {
                ret.push_back(s.top());
                s.pop();
            }
        }

        return ret;
    }
};

6.加减

 

综合性很强的一道题。

题意简单来说就是给了一个数组,还有最多k次操作机会,每次操作可以使数组的某个元素+1或者-1, 在经过最多k次操作后,使得数组中出现最多相同的数的次数,求出这个元素出现的次数。

思路: 

1.贪心思想:我们要尽可能的选择挨得近的数,这样把它们变成相同的数所花费的次数就会少。

那么我们可以先将数组进行排序,这样就可以让数组的元素尽可能挨得近。

2.之后,我们就可以枚举数组中所有的区间,找出使区间内所有数变成相同的数所花费的代价cost <= k,在这些区间中的最大区间。

如果直接暴力枚举,时间复杂度O(N^2),超时。

在枚举过程中,我们可以发现其left和right指针移动的单调性,所以可以用滑动窗口来优化,那么时间复杂度降为O(N)。

3.关于怎样计算区间的最小花费

这里引入一个数学问题:在一个数轴上有一些点,如何选取一个位置,使得所有的点到这个位置的距离之和最小?

答案是:选取中间的点,如果点的个数是偶数,那么中间任意两个点都可以。

所以将这个结论应用到这题

a1就是left,a5就是right所指向的位置,那么该区间的最少花费也就是所有点到a3的距离。

如果我们每次都要对每个点都计算的话,那么这里计算的时间复杂度为O(N),因此我们可以使用前缀和,把时间复杂度降为O(1)。

用sum数组代表前缀和数组。

原本求cost的公式化简后,下划线表示的是这个地方可以用前缀和来代替,并且发现了一个规律,其余的部分就是 (a3前有多少个点) * a3 - (a3后有多少个点) * a3 - (前缀和1) + (前缀和2) 

那么此时最小cost的计算公式为:

 这个公式对应了原本求cost化简后的例子。

并且发现其中还有可以化简的地方,我们在代码中体现了。 

代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

long long arr[N];
long long sum[N]; // 前缀和数组
long long n,k;

long long cal(int l,int r) // 计算区间的花费
{
    int mid = l + (r - l) / 2;
    return (2 * mid - l - r) * arr[mid] - (sum[mid - 1] - sum[l - 1]) + (sum[r] - sum[mid]);
}

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; ++i) // 使用了前缀和,那么下标从1开始比较方便
        cin >> arr[i];
    
    sort(arr + 1,arr + n + 1); // 使得元素之间挨得近
    
    for(int i = 1; i <= n; ++i) // 下标从1开始,可以防止越界
        sum[i] = sum[i - 1] + arr[i]; // 前缀和
    
    int left = 1,right = 1;
    int ret = 1;
    long long cost; // 区间的所需要花费的次数
    while(right <= n)
    {
        // 进窗口
        cost = cal(left,right);
        
        while(cost > k) // 判断
        {
            left++; // 出窗口
            cost = cal(left,right);
        }
        //更新结果
        ret = max(ret,right - left + 1);
        right++;
    }
    cout << ret << endl;
    
    return 0;
}

 所以本题的时间复杂度为 O(N logN)主要是排序所消耗的时间。

运用了枚举 + 前缀和 + 滑动窗口 + 贪心。并且还有数学问题的推导,公式的化简。

7.mari和shiny 

 

解法:动态规划

是多状态的线性dp。

其实只要把状态表示想明白就是一道比较简单的dp。

如果我们要找到能组成 ‘shy’的子序列,那么首先得知道有多少个 ‘sh’ 子序列,如果想知道有多少个 ‘sh’子序列,就需要知道有 多少个 's'。

那么就有三个状态,那么就存在三个dp表。

s[i] 表示在[0,i]区间有多少个 's'。

h[i] 表示在[0,i]区间有多少个 'sh'。

y[i]表示在[0,i]区间有多少个'shy'。

因为它的状态转移方程很简单,先填好s表,然后再填h表,最后再填y表,然后返回y表的结果。

所以就不多说了。三次for循环搞定。
另外有一个细节就是初始化,我们可以多开一个格子,方便填表。

还有就是本题数据量比较大,记得用 long long 类型来存。

代码:

#include <iostream>

using namespace std;

const int N = 3e5 + 10;
long long s[N],h[N],y[N];

int main()
{
    int n;
    string tmp;
    cin >> n >> tmp;

    for(int i = 1; i <= n; ++i)
    {
        s[i] = s[i - 1];
        if(tmp[i - 1] == 's')
            s[i] += 1;
    }
    for(int i = 1; i <= n; ++i)
    {
        h[i] = h[i - 1];
        if(tmp[i - 1] == 'h')
            h[i] += s[i];
    }
    for(int i = 1; i <= n; ++i)
    {
        y[i] = y[i - 1];
        if(tmp[i - 1] == 'y') 
            y[i] += + h[i];
    }
    cout << y[n] << endl;
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1951152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Codeforces 903 div3 A-F

A 题目分析 数据范围很小&#xff0c;暴力枚举即可&#xff0c;然后给字符串x的长度设置一个上限&#xff0c;我设了50&#xff0c;因为n*m<25&#xff0c;多一倍够用了 C代码 #include<iostream> using namespace std; void solve(){int n,m;string x,s;cin>>…

视频怎么加密?常见的四种视频加密方法和软件

视频加密是一种重要的技术手段&#xff0c;用于保护视频内容不被未经授权的用户获取、复制、修改或传播。在加密过程中&#xff0c;安企神软件作为一种专业的加密工具&#xff0c;可以发挥重要作用。 以下将详细介绍如何使用安企神软件对视频进行加密&#xff0c;并探讨视频加密…

VUE3学习第三篇:报错记录

1、在我整理好前端代码框架后&#xff0c;而且也启动好了对应的后台服务&#xff0c;访问页面&#xff0c;正常。 2、报错ReferenceError: defineModel is not defined 学到这里报错了 在vue网站的演练场&#xff0c;使用没问题 但是在我自己的代码里就出问题了 3、watchEffec…

等级保护测评解决方案

什么是等级保护测评&#xff1f; 网络安全等级保护是指对国家重要信息、法人和其他组织及公民的专有信息以及公开信息和存储、传输、处理这些信息的信息系统分等级实行安全保护&#xff0c;对信息系统中使用的信息安全产品实行按等级管理&#xff0c;对信息系统中发生的信息安全…

【NPU 系列专栏 3 -- NVIDIA 的 H100 和 H200 的算力介绍】

请阅读【嵌入式及芯片开发学必备专栏】 文章目录 NVIDIA H100 和 H200 的算力NVIDIA H100 芯片的算力NVIDIA H100 算力参数NVIDIA H100 举例 NVIDIA H200 芯片的算力NVIDIA H200 算力参数 H200 的内存和带宽提升H200 推理吞吐量提高H200 性能提升NVIDIA H200 举例Summary NVIDI…

DLMS/COSEM中公开密钥算法的使用_椭圆曲线加密法

1.概述 椭圆曲线密码涉及有限域上的椭圆曲线上的算术运算。椭圆曲线可以定义在任何数字域上(实数、整数、复数)&#xff0c;但在密码学中&#xff0c;椭圆曲线最常用于有限素数域。 素数域上的椭圆曲线由一组实数(x, y)组成&#xff0c;满足以下等式: 方程的所有解的集合构成…

C 语言动态链表

线性结构->顺序存储->动态链表 一、理论部分 从起源中理解事物&#xff0c;就是从本质上理解事物。 -杜勒鲁奇 动态链表是通过结点&#xff08;Node&#xff09;的集合来非连续地存储数据&#xff0c;结点之间通过指针相互连接。 动态链表本身就是一种动态分配内存的…

【C++】C++应用案例-翻转数组

翻转数组&#xff0c;就是要把数组中元素的顺序全部反过来。比如一个数组{1,2,3,4,5,6,7,8}&#xff0c;翻转之后就是{8,7,6,5,4,3,2,1}。 &#xff08;1&#xff09;另外创建数组&#xff0c;反向填入元素 数组是将元素按照顺序依次存放的&#xff0c;长度固定。所以如果想要…

全网最详细Gradio教程系列5——Gradio Client: javascript

全网最详细Gradio教程系列5——Gradio Client: javascript 前言本篇摘要5. Gradio Client的三种使用方式5.2 使用Gradio JavaScript Client5.2.1 安装1. npm方式2. CDN方式3. 在线运行环境&#xff1a;PLAYCODE 5.2.2 连接到Gradio程序1. 通过URL或SpaceID连接2. 辅助&#xff…

RuoYi-Vue-Plus (多数据源注解使用、【手动、拦截器】切换数据源)

接上文多数据源配置&#xff1a; RuoYi-Vue-Plus (多数据源配置)-CSDN博客 一、功能演示 代码生成菜单页面&#xff0c; 展示数据源切换 查询主库 查询从库 二、前端传参切换数据源 页面路径&#xff1a; src/views/tool/gen/index.vue 搜索框如下&#xff1a;下面4发送请求时…

SPICE | 常见电路SPICE模型总结

Ref. 1. CMOS VLSI Design: A Circuits and Systems Perspective 目录 0 基础 1 反相器 inverter 2 缓存器 buffer 3 NAND 4 NOR 5 传输门 Transmission gate 6 三态反相器 Tristate Inverter 7 选择器 Multiplexers 8 D锁存器 D Latch 9 D触发器 D Flip-Flop 0 基础…

Linux文件描述符

前言 我们以前就听过"Linux下一切皆文件"&#xff0c;但是说实话我们只是记住了这句话&#xff0c;实质是不理解的&#xff01;本期我们就会解释&#xff01; 本期内容介绍 • 回顾C语言文件操作 • 系统I/O操作接口 • 文件描述符fd • 理解Linux下一切皆文件 • …

如何设置postgresql数据库的账户密码

说明&#xff1a;在我的云服务器上&#xff0c;postgres是使用yum的方式安装的&#xff0c;不需要设置postgres账户的密码&#xff0c;本文介绍安装后如何手动设置postgres账户的密码&#xff1b; postgres数据库安装&#xff0c;参考下面这篇文章&#xff1a; PostgreSQL安装…

构建基于Spring Boot的SaaS应用

引言 在设计和实现SaaS系统时&#xff0c;安全性是至关重要的考虑因素。一个全面的安全策略不仅能保护系统免受恶意攻击&#xff0c;还能确保用户数据的机密性、完整性和可用性。本文将探讨在SaaS架构中实现数据加密、敏感信息保护以及应用安全的最佳实践和技术方案&#xff0…

【大模型】基于LoRA微调Gemma大模型(1)

文章目录 一、LoRA工作原理1.1 基本原理1.2 实现步骤 二、LoRA 实现2.1 PEFT库&#xff1a;高效参数微调LoraConfig类&#xff1a;配置参数 2.2 TRL库SFTTrainer 类 三、代码实现3.1 核心代码3.2 完整代码 参考资料 大模型微调技术有很多&#xff0c;如P-Tuning、LoRA 等&#…

Vue3计算属性终极实战:可媲美Element Plus Tree组件研发之节点勾选

前面完成了JuanTree组件的节点编辑和保存功能后&#xff0c;我们把精力放到节点勾选功能实现上来。**注意&#xff0c;对于组件的开发者来说&#xff0c;要充分考虑用户的使用场景&#xff0c;组件提供的多个特性同时启用时必须要工作良好。**就拿Tree组件来说&#xff0c;用户…

数据库(MySQL)-视图、存储过程、触发器

一、视图 视图的定义、作用 视图是从一个或者几个基本表&#xff08;或视图&#xff09;导出的表。它与基本表不同&#xff0c;是一个虚表。但是视图只能用来查看表&#xff0c;不能做增删改查。 视图的作用&#xff1a;①简化查询 ②重写格式化数据 ③频繁访问数据库 ④过…

如何学习Doris:糙快猛的大数据之路(从入门到专家)

引言:大数据世界的新玩家 还记得我第一次听说"Doris"这个名字时的情景吗?那是在一个炎热的夏日午后,我正在办公室里为接下来的大数据项目发愁。作为一个刚刚跨行到大数据领域的新手,我感觉自己就像是被丢进了深海的小鱼—周围全是陌生的概念和技术。 就在这时,我的…

江苏科技大学24计算机考研数据速览,有专硕复试线大幅下降67分!

江苏科技大学&#xff08;Jiangsu University of Science and Technology&#xff09;&#xff0c;坐落在江苏省镇江市&#xff0c;是江苏省重点建设高校&#xff0c;江苏省人民政府与中国船舶集团有限公司共建高校&#xff0c;国家国防科技工业局与江苏省人民政府共建高校 &am…