她真好看(雾)
博主是真的菜,大一月份才开始学,真正的零基础从入门到放弃。基本 的编程能力是有的,算法纯蒟蒻。
起因是看到学校集训队有宣传,所以就想着也报一下,顺便提高一下个人的代码能力。
算法基础集训营第一场 2.03
赛题:https://ac.nowcoder.com/acm/contest/120561
验题人题解:链接
第一场个人觉得发挥还行,题也比较偏数学推论,大体上找到规律就可以做了。只讲一讲调出来的题,其他太难的就不补了。
B - Card Game
这题要发现一个规律,就是小苯一场总共得多少分和小红手牌顺序是没关系的。所以只需要对小苯出牌顺序进行讨论。。然后发现得最多分的牌序规律。
思路:因为牌的数字是排列所以每次比较都是会有胜负的,不会出现平局。那么如果想让小苯尽可能赢的话就一定要让大的牌在前面,小的牌在后面(因为一旦对手小红的牌出完了游戏就结束了),而这里你就会发现小苯的牌是大是小有一个临界点——那就是小红的最小牌。比如如果小苯所有的牌都比小红最小的牌小,那么小苯无论如何都无法得分。而如果小苯存在比小红最小的牌大的牌就一定能得到分。由此我们可以根据这个临界点来划分小苯的牌是大是小两部分,然后通过阶乘求出两个部分所有的排列组合数,最后再把它们相乘就是结果了。
这里我通过遍历小红的牌(数组)找出最小的牌的值。然后用记录小苯的牌(数组)中比这个值大的数量,记录比这个值小的数量。分别求阶乘,然后再相乘。
NOTE根据题目要求,因为数据会比较大,所以求阶乘的时候要对每一步进行取模操作防止整型溢出。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define mod 998244353vector<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
这里最阴的其实是开区间这个条件。。喜提首发,发现规律之后大胆写就是。
思路:题目说的是将选定区间两端内的数都变成两端值的较大者,然后你玩了几个数据之后就会发现,总有办法让整个数组里面除了两个端点外的其他值都变成数组的最大值。而数组两端的值(即和的值无论如何都不能变成数组的最大值,那么答案就很简单了。。找出最大值乘,然后再加上和的值就是答案了。
这里没开数组所以用了新变量 和来存和的值。
#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
这题有环形链表内味了,不过其实可以简单实现。
思路:我们可以找几个数据来自己手动模拟一下这个插入挤出的过程。然后就会发现题目的意思可以转化为找数组所有元素加上共个数(算第一个数),每两个相邻的数的和的最大值是多少。那么我们就只需要遍历数组每两个相邻的数,然后取最大就可以了(首尾也算相邻也要判断)
初始化最大值记得设成-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
没错,这题调了一个多小时。。因为一直不知道怎么优化,后面发现还是直接找可能的最大值比较快,虽然实现比较复杂。。
思路:题意就是给你一个区间,让你找区间里面的数反转之后的最大值。一般来说直接构造一个反转数字的函数,然后在区间里面遍历判断,取最大值就可以了。。奈何这题的最大值开到了,直接遍历肯定超时了。这个时候我们通过观察可以注意到,最大的数一般是和同位数的,所以可能的最大值可以从右端点开始判断。并且要让小的位尽可能为,这样反转后的数才能尽可能大。然后还需要考虑端点取到最大的可能。
实现真的很复杂。。需要优化的技巧比较难想。。首先我们要判断一些经典的这种数是否在区间内(这是一种可能),记录最大值。然后我们开始构造可能的最大值。这里我的想法是从个位开始逐个判断能否取9,能取就记为最大值里。最后还要判断端点上的值是否可能是最大值。
NOTE构造的时候有个小技巧,如果直接将对应的位数替换为肯定是大于的,所以可以先将对应位数归零,再减,这样就能尽可能大的取到9且小于了。
#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
这题也是妙手偶得。。三个字,大胆猜。
思路:我们取几个数来看:只有的时候,显然成立。时,显然不成立。当时,显然成立。当时,,显然不成立。并且你会发现之后的数相乘会越来越大,数组元素求和远远小于乘积,不满足题意。所以满足题意的数据只有和其他的都不符合题意。
#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
这题就是简单的数学。。自己拿几个数玩两下也明白了。
思路:想要个位数为,就单看个位数就行了。如果个位是非零偶数就乘,个位是就乘,个位是就乘,其他的(,,,)就只能乘。
这里的判断其实还可以优化一下,不用像我这样一个个判断。
#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 - 比赛安排
一开始看错题了,以为是题意是两两不同。。首发两个,后面注意到是连续三个不同就简单了。
思路:因为只能是连续三个不同的类型,那么就只能是或者这样固定的顺序了,最后顶多结尾其中两种类型各多出一个或一种类型多出一个,例如结尾或者,因此只需要比较输入的三种类型比赛场数最大的和最小的差是否小于等于,小于等于就输出,大于就输出。
#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
这题也调了很久没调出来,调了两次都是超时,看了题解之后发现思路只对了一半,说明原来不仅只是实现的问题,连思路都是错的。。
思路:实际上就只需要对美味值最大的人数进行判断。如果是奇数那么所有美味值最大的人都是(可能获胜)。因为最后其他人都要和他比较大小,并且都会输。如果是偶数,那么除了美味值最大的那个人之外其他人都有可能赢。因为可以让其中一个最大美味值的人跟其他人都比较,然后只留下指定的那个人赢。
思路明确了之后其实实现对我来说也是一大难点,题解是使用了映射美味值和数组(用于存放下标),然后用了auto &[x, y] = *prev(mp.end());这个语法获取最大美味值的人的对应下标数组。然后根据下标数组元素的个数判断奇偶性和判断下标来构造字符串。
NOTE
auto &[x, y] = *prev(mp.end());auto &[x,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!
纯粹的数学与二进制,然而我并不会。。题解很详细,具体可以去看看,我这里就讲一下数学推导了。
思路:自己对样例里的一些数据玩一下就会发现一个规律: ,然后我们可以让保证是的倍数。我们可以通过两个数异或两次为本身的规律将 移项为 ,所以目前我们需要满足 ,这两个式子。继续展开:
这个式子可以直观理解,位或就是所有单为的位加上多算的同为的位和,位与就是同为的位的和,它们加起来根据二进制进位就是原来的式子的和。
这个式子同上,位或减去位与就只剩下不相同的为1了即位异或。
根据式子我们发现只需要令 , 就可以取到最大了。接下来我们就只需要构造满足条件的数就可以了。这里令 可以保证一定是的倍数。
#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卡到爆了,想过和只出现过一次就的情况,不过终究还是不知道怎么实现。后续
看了一眼题解,发现居然是用字符串简化遍历的流程,受教了。。
思路:事实上就是只要和在所给的矩阵中出现的次数超过就一定可以形成回文。比如说从开始,然后走到另一个上就一定是回文的了,也是同理。
后续补题的代码:
#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 - 宙天
这题直接把所有情况列出来就可以了,妥妥签到题。
思路,因为最大取到,那么可以自己手动推导所有的可能性然后判断,也可以写个循环判断输入的是否满足 。
#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的两个数,不过不会线性筛。。题解根据概率进行小范围枚举显然是更优的方案。。。
思路:可以从偶数这一块来入手。根据题目的数据说明,实际上数组元素都是均匀随机生成,那么偶数出现的概率就是 ,而根据题意,特殊情况下只要有两个偶数存在就一定满足。题目给到的数据最大可以取到,我们可以只取后个数与其进行比较,只要存在两个偶数就退出循环,输出结果。同理于质因数,我们用函数来判断,可以提高成功的概率。除非题目就是要卡死最坏情况(概率小到离谱的那种),一般这种情况下都可以过了。。
#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
这题就是找规律构造,奈何我捣鼓了个小时都没找到。。。
思路:小红会尽可能缩短路径需要的长度,可以发现她在前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 - スピカの天秤
这题比较容易理解,想要改变状态就尽可能多地拿走较大的砝码,只不过因为时间复杂度问题要优化求和跟排序。题解直接用的排序,我用的优先队列。
思路:当重量相等的时候随便拿走一个就可以改变状态了,所以输出。当重量不等的时候就让重量较大的一方优先拿走较大的砝码(贪心),记录打破状态时拿走砝码的数量就可以了。
这里一开始以为优先队列有超时风险(因为加起来复杂度应该是,不过好在并没有。
NOTE这里的优先队列会将最大的值放在顶端
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’
这题我只能说无敌了。。我用的数学公式是没问题的,只不过一直因为精度问题调不出来了,已疾苦。
思路:已知三个坐标求三角形面积公式:,然后根据题意知第三个点是 ,也就是 代入原式。可以得到有关的表达式子:。满足误差时输出结果即可。
这里坑就坑在精度。。要保证面积精度为,就须要更加精确,所以最保险是输出位小数。
同时对于无解的情况也要特判(的时候无解),并且还有精度要到-(阴完了)。
#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

思路:根据我们自己画的图可以发现每一层的节点数其实就对应 的值(根节点为,所以从开始),我们要做的就是判断所给的数在哪一层(判断这个数是否大于等比数列前项的和),在对应的那一层判断是否填满,未填满的就用减去前几层的和再加(等比数列求和再加)就是这一层所有的节点数了。查询的话同理判断就可以了,如果是最深层就输出未填满那一层的值。其他填满的层就直接输出对应层数的等比数列的值。
NOTE这里要注意长整型()最大值为,判断的时候只需要判断到是否大于就可以了,否则会溢出。
题目的输入最大取到,小于(大约)。
#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;}