4065 字
10 分钟
牛客寒假算法基础集训营赛后个人总结(上)
2026-02-10
统计加载中...

她真好看(雾)

博主是真的菜,大一1010月份才开始学C++C++,真正的零基础从入门到放弃。基本 的编程能力是有的,算法纯蒟蒻。

起因是看到学校ACMACM集训队有宣传,所以就想着也报一下,顺便提高一下个人的代码能力。

算法基础集训营第一场 2.03#

赛题:https://ac.nowcoder.com/acm/contest/120561 验题人题解:链接
第一场个人觉得发挥还行,题也比较偏数学推论,大体上找到规律就可以做了。只讲一讲调出来的题,其他太难的就不补了。

B - Card Game#

这题要发现一个规律,就是小苯一场总共得多少分和小红手牌顺序是没关系的。所以只需要对小苯出牌顺序进行讨论。。然后发现得最多分的牌序规律。
思路:因为牌的数字是排列所以每次比较都是会有胜负的,不会出现平局。那么如果想让小苯尽可能赢的话就一定要让大的牌在前面,小的牌在后面(因为一旦对手小红的牌出完了游戏就结束了),而这里你就会发现小苯的牌是大是小有一个临界点——那就是小红的最小牌。比如如果小苯所有的牌都比小红最小的牌小,那么小苯无论如何都无法得分。而如果小苯存在比小红最小的牌大的牌就一定能得到分。由此我们可以根据这个临界点来划分小苯的牌是大是小两部分,然后通过阶乘求出两个部分所有的排列组合数,最后再把它们相乘就是结果了。
这里我通过遍历小红的牌(bb数组)找出最小的牌的值。然后用cnt1cnt1记录小苯的牌(aa数组)中比这个值大的数量,cnt2cnt2记录比这个值小的数量。分别求阶乘,然后再相乘。

NOTE

根据题目要求,因为数据会比较大,所以求阶乘的时候要对每一步进行取模操作防止整型溢出。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define mod 998244353
vector<ll> a;
vector<ll> b;
void solved(){
int t;
cin >> t;
while(t--){
int n;
cin >> n;
ll mi = 400010;
ll cnt1 = 0,cnt2 = 0;
a.resize(n + 1);
b.resize(n + 1);
for (int i = 1; i <= n;i++){
cin >> a[i];
}
for (int i = 1; i <= n;i++){
cin >> b[i];
if(b[i] < mi) mi = b[i];
}
//cout << "mi:" << mi << '\n';
for (int i = 1; i <= n;i++){
if(a[i] < mi){
cnt1++;
}
else{
cnt2++;
}
}
//cout << "cnt1:" << cnt1 << " " << cnt2 << '\n';
ll ans1 = 1,ans2 = 1;
for (int i = 1; i <= cnt1;i++){
ans1 = ((ans1 % mod) * i) % mod;
}
for (int i = 1; i <=cnt2;i++){
ans2 = ((ans2 % mod) * i) % mod;
}
cout << ((ans1 % mod) * (ans2 % mod)) % mod << '\n';
}
}
int main(){
solved();
return 0;
}

C - Array Covering#

这里最阴的其实是开区间这个条件。。喜提首发WAWA,发现规律之后大胆写就是。
思路:题目说的是将选定区间两端内的数都变成两端值的较大者,然后你玩了几个数据之后就会发现,总有办法让整个数组里面除了两个端点外的其他值都变成数组的最大值。而数组两端的值(即a1a_1ana_n的值无论如何都不能变成数组的最大值,那么答案就很简单了。。找出最大值乘n2n-2,然后再加上a1a_1ana_n的值就是答案了。
这里没开数组所以用了新变量 t1t1t2t2来存a1a_1ana_n的值。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
ll n,ma = 0;
ll ans = 0;
ll t1, t2;
cin >> n;
for (int i = 1; i <= n;i++){
int x;
cin >> x;
if(i == 1)
t1 = x;
if(i == n)
t2 = x;
if(x > ma)
ma = x;
}
cout << ma * (n-2) + t1 + t2 << '\n';
}
}
int main(){
solved();
return 0;
}

E - Block Game#

这题有环形链表内味了,不过其实可以简单实现。
思路:我们可以找几个数据来自己手动模拟一下这个插入挤出的过程。然后就会发现题目的意思可以转化为找数组所有元素加上kkn+1n+1个数(kk算第一个数),每两个相邻的数的和的最大值是多少。那么我们就只需要遍历数组每两个相邻的数,然后取最大就可以了(首尾也算相邻也要判断)
初始化最大值记得设成-1e9,因为题目中每个数组元素最小值可以是-1e6。
我这里单独判断了首尾,也可以像题解那样取模判断。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
vector<ll> a;
void solved(){
int t;
cin >> t;
while(t--){
int n, k;
cin >> n >> k;
ll ans = -1e9;
a.resize(n + 2);
a[1] = k;
for (int i = 2; i <= n + 1;i++){
cin >> a[i];
}
for (int i = 1; i <= n + 1;i++){
if(i <= n){
ans = max(a[i] + a[i + 1],ans);
}
}
ans = max(ans, a[1] + a[n + 1]);
cout << ans << '\n';
}
}
int main(){
solved();
return 0;
}

G - Digital Folding#

没错,这题调了一个多小时。。因为一直不知道怎么优化,后面发现还是直接找可能的最大值比较快,虽然实现比较复杂。。
思路:题意就是给你一个区间[l,r][l,r],让你找区间里面的数反转之后的最大值。一般来说直接构造一个反转数字的函数,然后在区间里面遍历判断,取最大值就可以了。。奈何这题的rr最大值开到了1e151e15,直接遍历肯定超时了。这个时候我们通过观察可以注意到,最大的数一般是和rr同位数的,所以可能的最大值可以从rr右端点开始判断。并且要让小的位尽可能为99,这样反转后的数才能尽可能大。然后还需要考虑端点取到最大的可能。
实现真的很复杂。。需要优化的技巧比较难想。。首先我们要判断一些经典的9,99,9999,99,999这种数是否在区间内(这是一种可能),记录最大值。然后我们开始构造可能的最大值。这里我的想法是从个位开始逐个判断能否取9,能取就记为最大值里。最后还要判断端点上的值l,rl,r是否可能是最大值。

NOTE

构造的时候有个小技巧,如果直接将对应的位数替换为99肯定是大于rr的,所以可以先将对应位数归零,再减11,这样就能尽可能大的取到9且小于rr了。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll hw(ll x){
ll v = 0;
ll s = x;
while(s != 0){
ll x = s % 10;
v = v * 10 + x;
s /= 10;
}
return v;
}
void solved(){
int t;
cin >> t;
while(t--){
ll l, r;
cin >> l >> r;
ll ans = 0;
for (ll i = 9;i <= r;){
if(i >= l){
ans = max(ans, i);
}
i = i * 10 + 9;
}
for (ll i = 1; i <= r;){
ll cnt = (r / i) * i - 1;
if(cnt <= r&&cnt >= l){
ans = max(ans, hw(cnt));
}
i *= 10;
}
if(hw(l) > ans) ans = hw(l);
if(hw(r) > ans) ans = hw(r);
cout << ans << '\n';
}
}
int main(){
solved();
return 0;
}

K - Constructive#

这题也是妙手偶得。。三个字,大胆猜。
思路:我们取几个数来看:只有11的时候,显然成立。n=2n = 2时,1×21+21 \times 2 \neq 1 + 2 显然不成立。当n=3n = 3时,1×2×3=1+2+31 \times 2 \times 3 = 1 + 2 + 3显然成立。当n=4n = 4时,1×2×3×41+2+3+41 \times 2 \times 3 \times 4 \neq 1 + 2 + 3 + 4,显然不成立。并且你会发现n>=4n >= 4之后的数相乘会越来越大,数组元素求和远远小于乘积,不满足题意。所以满足题意的数据只有n=1n = 1n=3n = 3其他的都不符合题意。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
int n;
cin >> n;
if(n == 1 || n == 3){
cout << "YES" << '\n';
for (int i = 1; i <= n;i++){
cout << i << " ";
}
cout << '\n';
}
else{
cout << "NO" << '\n';
}
}
}
int main(){
solved();
return 0;
}

L - Need Zero#

这题就是简单的数学。。自己拿几个数玩两下也明白了。
思路:想要个位数为00,就单看个位数就行了。如果个位是非零偶数就乘55,个位是55就乘22,个位是00就乘11,其他的(11337799)就只能乘1010
这里的判断其实还可以优化一下,不用像我这样一个个判断。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int n;
cin >> n;
n %= 10;
if(n == 2 || n == 4 || n == 6 || n == 8)
cout << 5;
else if(n == 3 || n == 7 || n == 9 || n == 1)
cout << 10;
else if(n == 5)
cout << 2;
else if(n == 0)
cout << 1;
}
int main(){
solved();
return 0;
}

算法基础集训营第二场 2.05#

赛题:https://ac.nowcoder.com/acm/contest/120562 验题人题解:链接
本场本蒟蒻只a出了A题。。。神秘B题被卡了一个小时,一开始看到这神奇的通过率还以为。。。结果被摆了一道。后续又做了F题和I题,都没调出来,已疾苦。所以下面就只复盘了一下比赛时看过的题。

A - 比赛安排#

一开始看错题了,以为是题意是两两不同。。首发两个WAWA,后面注意到是连续三个不同就简单了。
思路:因为只能是连续三个不同的类型,那么就只能是ABCABCABCABC或者BACBACBACBAC这样固定的顺序了,最后顶多结尾其中两种类型各多出一个或一种类型多出一个,例如结尾AA或者ABAB,因此只需要比较输入的三种类型比赛场数最大的和最小的差是否小于等于11,小于等于就输出YESYES,大于就输出NONO

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
ll a, b, c;
cin >> a >> b >> c;
ll ma = max(a, max(b, c));
ll mi = min(a, min(b, c));
if(ma <= mi + 1){
cout << "YES" << '\n';
}
else{
cout << "NO" << '\n';
}
}
}
int main(){
solved();
return 0;
}

B - NCPC#

这题也调了很久没调出来,调了两次都是超时,看了题解之后发现思路只对了一半,说明原来不仅只是实现的问题,连思路都是错的。。
思路:实际上就只需要对美味值最大的人数进行判断。如果是奇数那么所有美味值最大的人都是11(可能获胜)。因为最后其他人都要和他比较大小,并且都会输。如果是偶数,那么除了美味值最大的那个人之外其他人都有可能赢。因为可以让其中一个最大美味值的人跟其他人都比较,然后只留下指定的那个人赢。
思路明确了之后其实实现对我来说也是一大难点,题解是使用了mapmap映射美味值xx和数组(用于存放下标),然后用了auto &[x, y] = *prev(mp.end());这个语法获取最大美味值的人的对应下标数组。然后根据下标数组元素的个数判断奇偶性和判断下标来构造字符串。

NOTE

auto &[x, y] = *prev(mp.end()); auto &[x,y] C++17C++17的结构化绑定语法,将mapmap的键和值自动放入变量 x,yx,y 里。
prev(mp.end())是返回的mp.end()迭代器的上一个迭代器,也就是最后一个元素的迭代器,*就是解引用里面的值。

题解代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
int T;
cin >> T;
while(T--){
int n;
cin >> n;
map<int, vector<int>> mp;
string s(n, '0');
for(int i = 0; i < n; i++){
int x;
cin >> x;
mp[x].push_back(i);
}
auto &[x, y] = *prev(mp.end());
int t = 1;
if(y.size() % 2 == 0){
t = -1;
s = string(n, '1');
}
for(auto &i : y){
s[i] += t;
}
cout << s << endl;
}
}

F - x?y?n!#

纯粹的数学与二进制,然而我并不会。。题解很详细,具体可以去看看,我这里就讲一下数学推导了。
思路:自己对样例里的一些数据玩一下就会发现一个规律:xy=gcd(x,y)=nx \bigoplus y = gcd(x,y) = n ,然后我们可以让y=x+ny = x + n保证yynn的倍数。我们可以通过两个数异或两次为本身的规律将 xy=nx \bigoplus y = n移项为 xn=yx \bigoplus n = y ,所以目前我们需要满足 y=x+ny = x + n ,y=xny = x \bigoplus n 这两个式子。继续展开:
y=x+n=(xn)+(x&n)y = x + n = (x \mid n) + (x\& n)
这个式子可以直观理解,位或就是所有单为11的位加上多算的同为11的位和,位与就是同为11的位的和,它们加起来根据二进制进位就是原来的式子的和。
y=xn=(xn)(x&n)y = x \bigoplus n = (x \mid n) - (x \& n)
这个式子同上,位或减去位与就只剩下不相同的为1了即位异或。
根据式子我们发现只需要令 x&n=0 x \& n = 0 , y=xny = x \bigoplus n 就可以取到最大了。接下来我们就只需要构造满足条件的数就可以了。这里令 x=n×232x = n \times 2 ^ {32} 可以保证xx一定是nn的倍数。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
ll n;
cin >> n;
cout << (n << 32) << " " << ((n << 32) + n) << '\n';
}
}
int main(){
solved();
return 0;
}

I - 01回文#

做这题的时候已经被B和F卡到爆了,想过0011只出现过一次就YY的情况,不过终究还是不知道怎么实现。后续 看了一眼题解,发现居然是用字符串简化遍历的流程,受教了。。
思路:事实上就是只要0011在所给的矩阵中出现的次数超过11就一定可以形成0101回文。比如说从11开始,然后走到另一个11上就一定是回文的了,00也是同理。
后续补题的ACAC代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
int n, m;
cin >> n >> m;
vector<string> s(n);
unordered_map<char, int> mp;
for (int i = 0; i < n;i++){
cin >> s[i];
for(char c: s[i]){
mp[c]++;
}
}
for (int i = 0; i < n;i++){
for(char c:s[i]){
if(mp[c] == 1){
cout << "N";
}
else{
cout << "Y";
}
}
cout << '\n';
}
}
}
int main(){
solved();
return 0;
}

算法基础集训营第三场 2.07#

赛题:https://ac.nowcoder.com/acm/contest/120563 出题人题解:链接
这一场相对而言还好,有比较基础的思维题,也有构造与证明的难题,虽然本蒟蒻只a出了3题。。不过赛后还是把看过的6题都补一下了。

A - 宙天#

这题直接把所有情况列出来就可以了,妥妥签到题。
思路,因为xx最大取到100100,那么可以自己手动推导所有的可能性然后判断,也可以写个循环判断输入的xx是否满足 x=k×(k+1)x = k \times (k + 1)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int x;
cin >> x;
if(x == 2 || x == 6 || x == 12 || x == 20 || x == 30 || x == 42 || x == 56 || x == 72 ||
x == 90){
cout << "YES";
}
else{
cout << "NO";
}
}
int main(){
solved();
return 0;
}

B - Random#

这题想过用暴力找所有质因数来判断是否有最大公约数大于1的两个数,不过不会线性筛。。题解根据概率进行小范围枚举显然是更优的方案。。。
思路:可以从偶数这一块来入手。根据题目的数据说明,实际上数组元素都是均匀随机生成,那么偶数出现的概率就是 12\frac{1}{2},而根据题意,特殊情况下只要有两个偶数存在就一定满足。题目给到的数据nn最大可以取到2e52e5,我们可以只取后3030个数与其进行比较,只要存在两个偶数就退出循环,输出结果。同理于质因数,我们用gcdgcd函数来判断,可以提高成功的概率。除非题目就是要卡死最坏情况(概率小到离谱的那种),一般这种情况下都可以过了。。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int a[200010];
void solved(){
int t;
cin >> t;
while(t--){
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
bool f = 0;
for (int i = 1; i <= n;i++){
for (int j = i + 1; j <= i + 31&& j <= n;j++){
if(__gcd(a[i],a[j]) > 1){
cout << a[i] << " " << a[j] << '\n';
f = 1;
break;
}
}
if(f == 1) break;
}
if(f == 0) cout << -1 << '\n';
}
}
int main(){
solved();
return 0;
}

F - Energy Synergy Matrix#

这题就是找规律构造,奈何我捣鼓了11个小时都没找到。。。
思路:小红会尽可能缩短路径需要的长度,可以发现她在前5次都可以通过先手优势构造最短路n1n-1步到达第nn列。而在第55次后无论如何她都会被小紫逼到换一次行(增加11步),到了第1010次后又会增加11次。所以结论就是最短路径n1n-1加上 [n5][\frac{n}{5}]。是的,就这样。。。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
ll n;
cin >> n;
cout << n - 1 + n / 5 << '\n';
}
}
int main(){
solved();
return 0;
}

G - スピカの天秤#

这题比较容易理解,想要改变状态就尽可能多地拿走较大的砝码,只不过因为时间复杂度问题要优化求和跟排序。题解直接用的排序,我用的优先队列。
思路:当重量相等的时候随便拿走一个就可以改变状态了,所以输出11。当重量不等的时候就让重量较大的一方优先拿走较大的砝码(贪心),记录打破状态时拿走砝码的数量就可以了。
这里一开始以为优先队列有超时风险(因为加起来复杂度应该是O(TNlogN)O(T \cdot N \cdot log N),不过好在并没有。

NOTE

这里的优先队列priority_queuepriority\_queue会将最大的值放在顶端pq.top(),剩下的值按从大到小依次向下排,所以拿走砝码时先减去pq.top()的值,再弹出值pq.pop(),下一次再调用pq.top()就会是第二大的值了。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
priority_queue<ll> pq1;
priority_queue<ll> pq2;
int n, m;
ll sum1 = 0, sum2 = 0;
cin >> n >> m;
for (int i = 1; i <= n;i++){
ll x;
cin >> x;
sum1 += x;
pq1.push(x);
}
for (int i = 1; i <= m;i++){
ll x;
cin >> x;
sum2 += x;
pq2.push(x);
}
int cnt = 0;
if(sum1 == sum2){
cout << 1 << '\n';
continue;
}
else if(sum1 > sum2){
while(sum1 > sum2){
sum1 -= pq1.top();
cnt++;
pq1.pop();
}
cout << cnt << '\n';
continue;
}
else if(sum1 < sum2){
while(sum1 < sum2){
sum2 -= pq2.top();
cnt++;
pq2.pop();
}
cout << cnt << '\n';
continue;
}
}
}
int main(){
solved();
return 0;
}

H - Tic Tac DREAMIN’#

这题我只能说无敌了。。我用的数学公式是没问题的,只不过一直因为精度问题调不出来了,已疾苦。
思路:已知三个坐标求三角形面积公式:S=12x1(y2y3)+x2(y3y1)+x3(y1y2)S = \frac{1}{2} \mid x_1(y_2 - y_3) + x_2(y_3 - y_1) + x_3(y_1 - y_2) \mid,然后根据题意知第三个点是 (x,0)(x,0),也就是 y=0 y = 0代入原式。可以得到有关xx的表达式子:x=(2Sx2y1+x1y2)÷(y1y2)x = (2S - x_2 y_1 + x_1 y_2) \div (y_1 - y_2)。满足误差时输出结果即可。
这里坑就坑在精度。。要保证面积精度为0.0010.001xx就须要更加精确,所以最保险是输出1010位小数。
同时对于无解的情况也要特判(y1y2y_1 - y_2的时候无解),并且还有精度要到1e1e-99(阴完了)。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
int q = y1 - y2;
ll ans = (ll)(x1 * y2) - (ll)(x2 * y1);
double x =(4.00000 - ans) / q;
double m = 0.5 * abs(x * (y1 - y2) + x1 * y2 - x2 * y1);
if(q == 0){
double t = fabs(x1 * y2 - x2 * y1);
if(fabs(t - 4.0) <= 1e-9) cout << 0 << '\n';
else cout << "no answer" << '\n';
return;
}
else{
printf("%.10f\n",x);
}
//printf("%.2f", m);
}
int main(){
solved();
return 0;
}

J - Branch of Faith#

替代文本
这题其实二叉树只是用来唬人的。。本质其实就是根据以22为公比,11为首项的等比数列进行判断和输出。
思路:根据我们自己画的图可以发现每一层的节点数其实就对应 2n2^n的值(根节点为00,所以从00开始),我们要做的就是判断所给的数在哪一层(判断这个数是否大于等比数列前nn项的和),在nn对应的那一层判断是否填满,未填满的就用nn减去前几层的和再加11(等比数列求和再加11)就是这一层所有的节点数了。查询的话同理判断就可以了,如果是最深层就输出未填满那一层的值。其他填满的层就直接输出对应层数的等比数列的值2n2^n

NOTE

这里要注意长整型(long longlong\ long)最大值为26312^{63}-1,判断的时候只需要判断到是否大于2622^{62}就可以了,否则会溢出。
题目的输入nn最大取到1e181e18,小于26312^{63}-1(大约9e199e19)。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solved(){
int t;
cin >> t;
while(t--){
ll n, q;
cin >> n >> q;
ll cnt = 0;
for (ll i = 0; i <= 61;i++){
if(n >= (1LL << (i + 1LL))){
cnt++;
}
else{
break;
}
}
//cout << "cnt:" << cnt << '\n';
while (q--){
ll x;
ll cnt1 = 0;
cin >> x;
for (ll i = 0; i <= 61;i++){
//cout << "cnt1:" << cnt1 << '\n';
if(cnt1 == cnt){
cout << n - ((1LL <<cnt)) + 1LL<< '\n';
break;
}
if(x >= (1LL << (i + 1LL))){
cnt1++;
}
else{
cout << (1LL << cnt1) << '\n';
break;
}
}
}
}
}
int main(){
solved();
return 0;
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

牛客寒假算法基础集训营赛后个人总结(上)
https://mkrari.cn/posts/nc_sum1/
作者
Mkrari
发布于
2026-02-10
许可协议
CC BY-NC-SA 4.0