2025牛客寒假算法基础集训营1补题笔记

news/2025/1/22 0:37:02/文章来源:https://www.cnblogs.com/Natural-TLP/p/18684517

题目难度顺序大致为:A D B G M H E J C F L K I

头疼的思维+模拟。

\(4\) 题写得挺顺,但 \(D\) 题没看清是两种元素出现次数相同wa了一发,\(M\) 题其实一开始没有思路但暴力写了一波奇迹的过了,赛后果然被hack数据太水,\(H\) 卡了4个钟。。。\(E\) 题明显的贪心结论没有套上,歪到平均数去了,然后就一直在 \(H\) 钻牛角尖。

A.茕茕孑立之影

题意

给定一个数组,找到一个正整数 \(x\),使得 \(x\) 和数组中的元素互不为倍数关系。

思路

  • 首先 \(1\) 是任何数的因数,所以有 \(1\) 的时候没有答案。
  • 然后考虑没有 \(1\) 的情况,可以发现只需要找到一个比数组中的元素都大的质数就可以,因为数组元素都不超过 \(10^9\) ,直接输出 \(1000000007\) 即可。

代码

#include <iostream>using namespace std;const int P = 1e9 + 7;int n;void solve()
{bool flag = 0;cin >> n;for (int i = 1; i <= n; i ++) {int x;cin >> x;if (x == 1) flag = 1;}if (flag) cout << -1 << '\n';else cout << P << '\n';
}int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t --) solve();return 0;
}

D.双生双宿之决

题意

给定一个数组,判断是否为双生数组,即元素种类数为 \(2\)、且出现次数相同。

思路

按题意模拟即可。用 \(set\) 来筛选种类个数,用 \(map\) 来记录每个数出现次数。
也可以排序,检查前半部分和后半部分数是否相等即可。

代码

#include <iostream>
#include <algorithm>
#include <map>
#include <set>#define si(x)       int(x.size())
#define fi          first
#define se          secondusing namespace std;int n;void solve()
{cin >> n;set<int> v;map<int, int> mp;for (int i = 0; i < n; i ++) {int x;cin >> x;mp[x] ++;v.insert(x);}if (n % 2 || si(v) != 2) cout << "No" << '\n';else {int num = 0;for (auto it : mp) {if (num == 0) num = it.se;else if (num != it.se) {cout << "No" << '\n';return ;}}cout << "Yes" << '\n';}
}int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t --) solve();return 0;
}

代码2

void solve()
{cin >> n;for (int i = 1; i <= n; i ++) cin >> a[i];sort(a + 1, a + 1 + n);if (n % 2 || a[1] == a[n]) return void(cout << "No" << '\n');if (a[1] == a[n / 2] && a[n / 2 + 1] == a[n]) cout << "Yes" << '\n';else  cout << "No" << '\n';
}

B.一气贯通之刃

题意

给一棵树,找到一条路径经过所有节点。

思路

自己手动画几棵树可以发现:如果一棵树的某个节点出度超过 \(2\) ,即这个节点与至少 \(3\) 个节点有连边,那么就不存在有简单路径是经过所有节点的,所以我们只需要去遍历一遍所有节点的出度就可以了。而起点、终点,则明显是两个叶子节点,出度为 \(1\)

题外知识:一颗树的最长简单路径就是这棵树的直径。可以用树形 \(dp\) 来解决。

代码

#include <iostream>using namespace std;const int N = 1e6 + 10;int n, u, v;
int a[N];void solve()
{cin >> n;for (int i = 1; i < n; i ++) {cin >> u >> v;a[u] ++, a[v] ++;}int sd = -1, ed = -1;for (int i = 1 ; i <= n; i ++) {if (a[i] > 2) return void(cout << -1 << '\n');if (a[i] == 1) if (sd == -1) sd = i;else ed = i;}cout << sd << ' ' << ed;
}int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;while (t --) solve();return 0;
}

G.井然有序之衡

题意

给一个数组,每次操作可以使一个元素加 \(1\),另一个元素减 \(1\) ,问变成排列的最小操作次数。

思路

首先,一个元素加 \(1\), 一个元素减 \(1\),对于数组总和是不变,所以数组是否可以构造成排列,在于数组总和和排列总和是否相等。然后是计算最小操作数。

贪心的方法解决最小操作数。
将数组进行升序排序,然后按 \(1 \sim n\) 的排列顺序计算操作个数。

代码

#include <iostream>
#include <algorithm>#define ll  long longusing namespace std;const int N = 1e6 + 10;ll n;
ll a[N];void solve()
{cin >> n;ll sum = 0;for (int i = 1; i <= n; i ++) {cin >> a[i];sum += a[i];}ll num = (n + 1) * n / 2;if (num != sum) cout << -1;else {sort(a + 1, a + 1 + n);ll res = 0;for (int i = 1; i <= n; i ++) res += abs(i - a[i]);cout << res / 2;}
}int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;while (t --) solve();return 0;
}

M.数值膨胀之美

题意

给定一个数组,可以选择一个区间将所有元素乘 \(2\),问操作后的最小极差。

思路

赛后重新思考,想到可以从第一个最小值开始维护区间,到最后包括所有最小值。

如何维护呢?

  • 首先存下所有元素的值和下标,升序排序。

  • 然后从第一个最小值下标开始,按区间右端点增大方向操作,到达下一个最小值的位置,区间内的数都要乘2,直到包括所有的最小值后结束。

最后这个思路只过了86.11%,看完题解才知道还要继续考虑次小值直到最大值。
(其实赛时已经发现假设选取所有元素乘2可能比选取子区间要更优,但赛后忘了。。。)

代码

#include <iostream>
#include <algorithm>#define fi first
#define se secondusing namespace std;typedef pair<int, int> PII;const int N = 1e5 + 10;int n, b[N];
PII a[N];int main()
{cin >> n;for (int i = 1; i <= n; i ++) {cin >> b[i];a[i] = {b[i], i};}sort(a + 1, a + 1 + n);int res = 0x3f3f3f3f;a[n + 1].fi = res;int maxv = a[n].fi, l = a[1].se, r = a[1].se;for (int i = 1; i <= n; i ++) {while (a[i].se <= l) maxv = max(maxv, b[l --] * 2);while (a[i].se >= r) maxv = max(maxv, b[r ++] * 2);res = min(res, maxv - min(a[1].fi * 2, a[i + 1].fi));}cout << res;return 0;
}

H.井然有序之窗

题意

构造一个排列,满足每个元素都在一个指定的区间内。

思路

一个经典的贪心题吧,居然在这跑dfs,感觉我赛时一定是脑子抽风了。

先说结论:第 \(i\) 个位置如果多个选择,那么选择区间右端点最小的那个数,结果一定不会更劣。

为什么呢?可以自己模拟一下:

假设现在要选择一个数填入第5的位置,有3种选择:3[3, 7]、6[4, 5]、8[5, 6]。

首先我们得知道既然已经到填入第5的位置了,那么 \(1\sim4\) 的位置都已经完成填入了,所以对于这4种选择,可以发现3和6的区间是要更小的:3[5, 7]、6[5, 5]。

所以这个位置如果先选3或8填入,那么6就无法填入了,而如果每个位置的多种选案都选右端点最小的填入,那么对后面的位置影响是最小的。

实现用优先队列来维护右端点的小根堆,枚举 \(1 \sim n\)的位置,将在这个位置下的所有未选区间放入队列中,如果没有或队首的右端点小于当前位置,就没有方案可行。

代码

#include <iostream>
#include <algorithm>
#include <queue>using namespace std;const int N = 1e6 + 10;int n;
struct node {int val;int l, r;bool operator < (const node& b) const {return r > b.r;}
} a[N];
priority_queue<node> pq;
int ans[N];bool cmp(node aa, node bb) {if (aa.l == bb.l) return aa.r < bb.r;return aa.l < bb.l;
}void solve()
{cin >> n;for (int i = 1; i <= n; i ++) {int l, r;cin >> l >> r;a[i] = {i, l, r};}sort(a + 1, a + 1 + n, cmp);for (int i = 1, j = 1; i <= n; i ++) {while (j <= n && a[j].l <= i) pq.push(a[j ++]);if (pq.empty() || pq.top().r < i) return void(cout << -1);ans[pq.top().val] = i;pq.pop();}for (int i = 1; i <= n; i ++) cout << ans[i] << ' ';
}int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);solve();return 0;
}

E.双生双宿之错

题意

给定一个数组,每次操作可以使得一个元素加1或者减1,问最小操作几次可以变成双生数组,即元素种类数为2、且出现次数相同。

思路

\(D\) 题的扩展,其实是一个贪心结论题,参考货仓选址。叫中位数定理,这个今天才知道。

先说结论:求一个数 \(x\),让一组元素与 \(x\) 的差的绝对值的和最小,那么 \(x\) 是这组元素的中位数,结果不会更劣。

我们依旧先举例模拟:有两个数3、5,中位数可以是3或5,那么差值和就是2,假如我们在大于5或小于3的范围内选一个数,比如7,那么差值和就是4+2=6比2大。

其实我们可以发现选择的那个数可以让大于它和小于它的数相抵消,如果某方有多出的数就会多增加差值,就上面的例子:5 - 3 = (4 - 3) + (5 - 4)= |3 - 5|,中间的|3 - 4| + (5 - 4)其实就是5-3,-4和+4相抵消了,如果是7变为:|3 - 7| + |5 - 7|相对于4多加了两个(7 - 5)。

有了上面的结论,解决这道题就很容易了,先将数组排序,找出前后两部分的中位数,然后求差的绝对值之和。但要处理两个中位数相等的特殊情况,可以枚举四种情况:假设前半部分的中位数为lmid,后半部分中位数为rmid,那么算出(lmid-1,rmid)、(lmid+1,rmid)、(lmid,rmid-1)、(lmid,rmid+1)的结果然后取最小值。

代码

#include <iostream>
#include <algorithm>using namespace std;typedef long long ll;const int N = 1e5 + 10;int n;
int a[N];void solve()
{cin >> n;int m = n / 2;for (int i = 1; i <= n; i ++) cin >> a[i];sort(a + 1, a + 1 + n);if (a[1] == a[n]) return void(cout << m << '\n');int midl = a[(m + 1) >> 1], midr = a[(m + 1 + n) >> 1];bool flag = 0;if (midl == midr) midl --, flag = 1;ll ans = 0;for (int i = 1; i <= m; i ++) ans += abs(a[i] - midl);for (int i = m + 1; i <= n; i ++) ans += abs(a[i] - midr);if (flag) {midl ++, midr ++;ll sum = 0;for (int i = 1; i <= m; i ++) sum += abs(a[i] - midl);for (int i = m + 1; i <= n; i ++) sum += abs(a[i] - midr);ans = min(ans, sum);midl ++, midr --;sum = 0;for (int i = 1; i <= m; i ++) sum += abs(a[i] - midl);for (int i = m + 1; i <= n; i ++) sum += abs(a[i] - midr);ans = min(ans, sum);midl --, midr ++;sum = 0;for (int i = 1; i <= m; i ++) sum += abs(a[i] - midl);for (int i = m + 1; i <= n; i ++) sum += abs(a[i] - midr);ans = min(ans, sum);}cout << ans << '\n';
}int main()
{int t;cin >> t;while (t --) solve();return 0;
}

J.硝基甲苯之袭

题意

给定一个数组,问有多少对元素满足它们的gcd等于xor。

思路

一个很有趣的题,涉及到数论,我赛后写了一下发现不难。

首先

\[x \oplus y = gcd(x, y)\\ \Rightarrow gcd(x, y) ~ | ~ x 、gcd(x, y) ~ | ~ y \]

然后,假设 \(i = x \oplus y = gcd(x, y)\),根据异或的性质有

\[x = i \oplus y \\ 则 ~ i = gcd(i \oplus y, y) \]

此时可以发现,\(i\) 是整除 \(y\) 的,我们可以枚举 \(i\)时,处理 \(i\) 的所有倍数,将符合上述等式且是给出的数组中的元素,那么就是一对方案,最后求和结果要除以2,因为 \(i \oplus y\)\(y\) 都是数组中的元素那么就会重复算两遍。

然后是关于枚举的双重循环

for (int i = 1; i < N; i ++)for (int j = i; j < N; j += i)  

这其实是一个和调和级数有关的时间复杂度。
即:

\[\frac{N}{1} + \frac{N}{2} + \frac{N}{3} + \dots + \frac{N}{N - 1} + \frac{N}{N} \\ \Rightarrow N \times(\frac{1}{1} + \frac{1}{2} + \frac{1}{3} + \dots + \frac{1}{N - 1} + \frac{1}{N}) \\ 而 ~ \frac{1}{1} + \frac{1}{2} + \frac{1}{3} + \dots + \frac{1}{N - 1} + \frac{1}{N}就是调和级数,当N无穷大时结果约等于ln(N) \\ 所以上面的双重循环的时间复杂度可以看作是N倍调和级数即O(N \times ln(N))。 \]

代码

#include <iostream>
#include <algorithm>using namespace std;typedef long long ll;const int N = 2e5 + 10;int n, x;
ll cnt[N];int main()
{cin >> n;for (int i = 0; i < n; i ++) {cin >> x;cnt[x] ++;}ll ans = 0;for (int i = 1; i < N; i ++)for (int j = i; j < N; j += i) if ((i ^ j) < N && __gcd(i ^ j, j) == i) ans += cnt[i ^ j] * cnt[j];cout << ans / 2;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探讨视图更新:提升数据库灵活性的关键技术

title: 深入探讨视图更新:提升数据库灵活性的关键技术 date: 2025/1/21 updated: 2025/1/21 author: cmdragon excerpt: 在现代数据库的管理中,视图作为一种高级的抽象机制,为数据的管理提供了多种便利。它不仅简化了复杂查询的过程,还能用来增强数据的安全性,限制用户对…

标准制修订信息管理系统:开启标准化管理的智能新时代

在当今快速发展的商业环境中,标准化工作对于企业的高效运营、质量提升以及行业竞争力的增强至关重要。然而,传统标准化管理方式往往面临着诸多痛点,如缺乏完善的动态管理体系、信息分散、查询与实施监督困难等。针对这些挑战,标准制修订信息管理系统应运而生,它以强大的技…

Catlike Coding Custom SRP笔记 - 平行光

原文链接:Directional Lights效果图 CustomRenderPipelineAsset.cs[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")] public class CustomRenderPipelineAsset : RenderPipelineAsset {public bool useDynamicBatching = true; //启用动态合批pu…

uart串口的低速通信基础知识及模块代码(来自正点原子P15)

正点原子P15在PL端的uart电路参考,PS端uart和PL端一致,这里不做重复,uart电路由电脑端进行供电,即uart和主芯片之间除利用uart_tx和uart_rx通信外是独立的。从上图中可以看到,FPGA芯片的PL_UART1_TX连接到CH340的RXD管脚,FPGA芯片的PL_UART1_RX连接到 CH340 的 TXD 管脚,…

I/O框架

流的概念、流的分类、字节流、字符编码、字符流、打印流、转换流和File类。流的概念概念:内存与存储设备之间传输数据的通道。流的分类按方向【重点】输入流:将<存储设备>中的内容读入到<内存>中。 输出流:将<内存>中的内容写入到<存储设备>中。按单…

网站向顾客发送电子邮件

首先说一下,针对顾客未登录就可下单这个功能,为了使用户可以实时知晓货品的物流状态,使用了advance shipment tracking这个插件,这个插件不仅可以显示货品的物流信息,还可以在货品物流状态更新时向顾客发送电子邮件,这样就实现了顾客在未登录时就可以知道自己购买的商品的…

2025.1.20——1300

2025.1.20——1300A 1300 You are given a binary string \(s\). A binary string is a string consisting of characters 0 and/or 1. You can perform the following operation on \(s\) any number of times (even zero):choose an integer \(i\) such that \(1 \le i \le |…

制作docker 镜像上传到docker hub仓库

注册docker hub账号 https://hub.docker.com/ 参照此篇:https://www.cnblogs.com/yjlch1016/p/8998479.htmldocker hub上创建仓库https://hub.docker.com/repositories 本地制作镜像并上传在本地登陆 docker hub 帐号docker login将容器commit 成镜像,可以先用docker …

虚拟现实国标解读系列(一)帧率

大家好,我是ij(我的网名),中文名叫林志宏。 遵循我一贯的年底必摸鱼的习惯,我打算开始摸鱼来水一些文章,安慰下自己过去的一年。 有关注过我,听我吹过牛的都知道,我在几年前,也不知道几年前, 反正long long ago,我参与起草过一份牛逼的测试标准,国标GB/T 38258《虚…

【Linux网络】深入理解linux内核网络性能优化

一、网络请求优化 1.1 减少不必要的网络IO 在系统设计与开发过程中,应尽量避免不必要的网络I/O操作,尤其是在可以通过本地进程或内存内完成的场景下,避免使用网络通信来实现。网络虽然是现代分布式系统中的核心组件,能够连接不同模块、简化开发流程,并支持大规模系统的构建…

【Java开发】简化Maven项目依赖:优雅去除未使用Jar包

一、为什么要做这件事? 自从我踏入职场,便历经了技术革新的数次浪潮。从最初的.Net Framework、Winform、WPF,到Asp.Net MVC、Asp.Net MVC WebApi,再到Asp.Net Core 2.x的广泛应用,我始终深耕于.net领域。 然而,随着技术的不断演进,我逐渐发现.net相关的工作机会变得稀少…

《操作系统真相还原》实验记录2.7——生产者与消费者问题

本节实现内容如下: ① 环形缓存区的结构体创建; ② 环形键盘缓冲区的创建; ③ 生产者消费者问题剖析;一、生产者与消费者问题简述我们知道,在计算机中可以并行多个线程,当它们之间相互合作时,必然会存在共享资源的问题,这是通过“线程同步”来解决的,而诠释“线程同步…