A-0子2023:
题目:
小蓝在黑板上连续写下从 11 到 20232023 之间所有的整数,得到了一个数字序列: 𝑆=12345678910111213...20222023S=12345678910111213...20222023。 小蓝想知道 𝑆S 中有多少种子序列恰好等于 20232023?
以下是 33 种满足条件的子序列(用中括号标识出的数字是子序列包含的数字):
1[2]34567891[0]111[2]1[3]14151617181920212223...1[2]34567891[0]111[2]1[3]14151617181920212223...
1[2]34567891[0]111[2]131415161718192021222[3]...1[2]34567891[0]111[2]131415161718192021222[3]...
1[2]34567891[0]111213141516171819[2]021222[3]...1[2]34567891[0]111213141516171819[2]021222[3]...
注意以下是不满足条件的子序列,虽然包含了 22、00、22、33 四个数字,但是顺序不对:
1[2]345678910111[2]131415161718192[0]21222[3]...1[2]345678910111[2]131415161718192[0]21222[3]...
笔记:
这道题的思路就是有点动规的思想在里面:因为我们可以通过模拟一遍看出,当我们选到了第n个‘2’时,那么此时在这个第n个2的后面出现的第一个‘0’就会组成n个‘20’的组合也就是将前面‘2’出现的次数在累加到当前的‘0上’,也就是排列组合不过是有顺序的:
这里我们就建立一个大小为4的dp数组:每一个数代表第几个下标,dp【0】就表示组成‘2’的数量,dp[4]就表示组成‘2024’的数量:
再者,由于我们每一次输入的都是一个数字可能是以为也可能是四位,所以我们要想将其转化为字符串来进行处理,这里我们采用stringstream的方式,通过读入这些不同位数的数字存入stringstream内,然后再将其转为字符串类型:
stringstream ss;
for(int i = 0; i <= 2023; i++){ss << i;
}
string st = ss.str();
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int main()
{stringstream ss;for(int i = 0; i <= 2023; i++){ss << i;}string st = ss.str();vector<ll> dp(4, 0);long long n = st.size();for(ll i = 0; i < n; i++){if(st[i] == '2'){dp[0]++;dp[2] += dp[1];}else if(st[i] == '0'){dp[1] += dp[0];}else if(st[i] == '3'){dp[3] += dp[2];}}cout << dp[3] << endl;return 0;
}
细细琢磨一下会发现非常妙,首先我们对首位元素‘2’:当我们遇到‘2’时,dp[0]++,dp[2]+=dp[1],这表示我们可以将这个‘2’当做是首也可以当做是中间的元素来处理。
B - 0双子数:
题目:
若一个正整数 𝑥x 可以被表示为 𝑝2×𝑞2p2×q2,其中 𝑝p、𝑞q 为质数且 𝑝≠𝑞p=q,则 𝑥x 是一个双子数。请计算区间 [2333,23333333333333][2333,23333333333333] 内有多少个双子数?
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
笔记:
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;int main(){// p * q < sqrt(max)ll max = 23333333333333;ll min = 2333;// pow(p * q, 2) <= max, 只要p * q < sqrt(max), 我们用到的数字只有p和q所以只判断这两个数是否为质数即可ll lim = 1e7;// 平方后肯定大于max// 埃氏筛模板:vector<bool> is_zhi(lim, true);is_zhi[0] = is_zhi[1] = false;// 到lim的范围前的两个因数必然是一个大于平方根,一个小于平方根。// 遍历完小于平方根的数那么也就同事遍历完了所以大于平方根的数// 找到p * q范围内所有的质数。for(ll i = 2; i * i < lim; i++){// 遍历小数部分,小于平方根if(is_zhi[i]){for(ll j = i * i; j < lim; j += i){// 遍历大数部分,大于平方根is_zhi[j] = false;}}}ll count = 0;for(ll i = 0; i < lim; i++){if(is_zhi[i]){if((i * i) > max) break;for(ll j = i + 1; j < lim; j++){if(is_zhi[j]){if(pow(i * j, 2) > max) break;else if(pow(i * j, 2) < min) continue;count++;}}}}cout << count;return 0;
}
C - 0班级活动:
题目:
小明的老师准备组织一次班级活动。班上一共有 𝑛n 名 (𝑛n 为偶数) 同学,老师想把所有的同学进行分组,每两名同学一组。为了公平,老师给每名同学随机分配了一个 𝑛n 以内的正整数作为 idid,第 𝑖i 名同学的 idid 为 𝑎𝑖ai。
老师希望通过更改若干名同学的 idid 使得对于任意一名同学 𝑖i,有且仅有另一名同学 𝑗j 的 idid 与其相同 (𝑎𝑖=𝑎𝑗ai=aj)。请问老师最少需要更改多少名同学的 idid?
输入格式
输入共 22 行。
第一行为一个正整数 𝑛n。
第二行为 𝑛n 个由空格隔开的整数 𝑎1,𝑎2,...,𝑎𝑛a1,a2,...,an。
输出格式
输出共 11 行,一个整数。
笔记:
这道题分三种情况讨论:
(1)当前id数量 < 2:这就说明了必然需要一个与其配对进行修改操作。
(2)当前id数量 > 2:这就说明只有前两位同学可以不用修改id,其余所有人都需要修改id,又因为我们需要一个来个id数量小于2的进行配对,所以我们需要拿出对应数量的id去与其配对。
(3)当前id数量等于2:这就说明不需要配对即可。
当我们的总的id>2的数量大于id<1的数量时,我们的最终修改次数就是id>2的数量减去内部已经配对成功的数量(2),如果id>2的数量小于id < 1的数量,那我们就需要进行处理:
sum = (sum_o - sum_n) / 2 + sum_n;
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int main()
{ll n;cin >> n;vector<ll> ele(n);for(ll i = 0; i < n; i++){cin >> ele[i];}// 每个元素的计数器:vector<ll> count(1e6, 0); // 注意当输入的数值大于等于n会越界的!!!!for(ll i = 0; i < n; i++){ll x = ele[i];count[x]++;}// 统计个数大于2和小于2的数量:ll count_min = 0;//数量小于2的总数ll count_max = 0;//数量大于2的总数for(ll i = 0; i < 1e5 + 5; i++){ // 注意这里选择数字的范围尽量大一点,覆盖题目所给数字的所有范围:if(count[i] == 1) count_min++;else if(count[i] > 2) count_max += (count[i] - 2);}ll res = 0;if(count_min < count_max){res = count_max;}else{res = (count_min - count_max) / 2 + count_max;}cout << res;return 0;
}
D - 0合并数组:
题目:
小明发现有很多方案可以把一个很大的正整数拆成若干正整数的和。他采取了其中两种方案,分别将他们列为两个数组 {𝑎1,𝑎2,...,𝑎𝑛}{a1,a2,...,an} 和 {𝑏1,𝑏2,...,𝑏𝑚}{b1,b2,...,bm}。两个数组的和相同。
定义一次合并操作可以将某数组内相邻的两个数合并为一个新数,新数的值是原来两个数的和。小明想通过若干次合并操作将两个数组变成一模一样,即 𝑛=𝑚n=m 且对于任意下标 𝑖i 满足 𝑎𝑖=𝑏𝑖ai=bi。请计算至少需要多少次合并操作可以完成小明的目标。
输入格式
输入共 33 行。
第一行为两个正整数 𝑛n, 𝑚m。
第二行为 𝑛n 个由空格隔开的整数 𝑎1,𝑎2,...,𝑎𝑛a1,a2,...,an。
第三行为 𝑚m 个由空格隔开的整数 𝑏1,𝑏2,...,𝑏𝑚b1,b2,...,bm。
输出格式
输出共 11 行,一个整数。
笔记:
这道题有一个关键点在于:合并的两个数必须是相邻的,这样会大大降低了我们的思考难度,所以我们的暴力策略就是:遍历这两个数组,如果a[i] > b[i]那么我们就将b[i]加上b[i + 1]一直到与a[i]相等,
#include <bits/stdc++.h>
using namespace std;
int main()
{int n, m;cin >> n >> m;vector<int> a(n);vector<int> b(m);for(int i = 0; i < n; i++){cin >> a[i];}for(int i = 0; i < m; i++){cin >> b[i];}int u = 0;int v = 0;int cura = a[0];int curb = b[0];int count = 0;while(u < n && v < m){if(cura == curb){cura = a[++u];curb = b[++v];}else if(cura < curb){cura += a[++u];count++;}else if(cura > curb){curb += b[++v];count++;}}cout << count;return 0;
}
反过来也一样,所以我们需要两个变量分别存储当前遍历到的a[i]和b[i],
E - 0数三角:
题目:
小明在二维坐标系中放置了 𝑛n 个点,他想在其中选出一个包含三个点的子集,这三个点能组成三角形。然而这样的方案太多了,他决定只选择那些可以组成等腰三角形的方案。请帮他计算出一共有多少种选法可以组成等腰三角形?
输入格式
输入共 𝑛+1n+1 行。
第一行为一个正整数 𝑛n。
后面 𝑛n 行,每行两个整数 𝑥𝑖xi, 𝑦𝑖yi 表示第 𝑖i 个点的坐标。
输出格式
输出共 11 行,一个整数。
笔记:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool check(const pair<ll, ll>& a, const pair<ll, ll>& b, const pair<ll, ll>& c){ll dx1 = a.first - b. first;ll dy1 = a.second - b.second;ll dx2 = a.first - c.first;ll dy2 = a.second - c.second;ll dx3 = b.first - c.first;ll dy3 = b.second - c.second;if((dx1 * dx1 + dy1 * dy1 == dx2 * dx2 + dy2 * dy2) || (dx1 * dx1 + dy1 * dy1 == dx3 * dx3 + dy3 * dy3) || ((dx2 * dx2 + dy2 * dy2 == dx3 * dx3 + dy3 * dy3))){return true;}return false;
}
bool is_line(const pair<ll, ll>& a, const pair<ll, ll>& b, const pair<ll, ll>& c){return (b.second - a.second) * (a.first - c.first) == (b.first - a.first) * (a.second - c.second);
}int main(){int n;ll count = 0;cin >> n;vector<pair<ll, ll>> dots(n);for(int i = 0; i < n; i++){cin >> dots[i].first >> dots[i].second;}for(int i = 0; i < n; i++){for(int j = i + 1; j < n; j++){for(int k = j + 1; k < n; k++){if(check(dots[i], dots[j], dots[k]) && !is_line(dots[i], dots[j], dots[k])){count++;}}}}cout << count;return 0;
}
G - 0AB路线:
题目:
有一个由 𝑁×𝑀N×M 个方格组成的迷宫,每个方格写有一个字母 A 或者 B。小蓝站在迷宫左上角的方格,目标是走到右下角的方格。他每一步可以移动到上下左右相邻的方格去。
由于特殊的原因,小蓝的路线必须先走 𝐾K 个 A 格子、再走 𝐾K 个 B 格子、再走 𝐾K 个 A 格子、再走 𝐾K 个 B 格子......如此反复交替。
请你计算小蓝最少需要走多少步,才能到达右下角方格? 注意路线经过的格子数不必一定是 𝐾K 的倍数,即最后一段 A 或 B 的格子可以不满 𝐾K 个。起点保证是 A 格子。
例如 𝐾=3K=3 时,以下 33 种路线是合法的:
AAA
AAAB
AAABBBAAABBB
以下 33 种路线不合法:
ABABAB
ABBBAAABBB
AAABBBBBBBAAA
输入格式
第一行包含三个整数 𝑁N、𝑀M 和 𝐾K。
以下 𝑁N 行,每行包含 𝑀M 个字符 ( A 或 B ),代表格子类型。
输出格式
一个整数,代表最少步数。如果无法到达右下角,输出 −1−1。
笔记:
这道题的重点在于bfs的规则:(1)每个点可以走多次(2)每一次移动需要连续走k次相同的元素从上面两点我们可以得知当我们遇到哪一点情况时需要跳过:从起点出发我们该点的cnt也就是连续走的步数为1,当我们想四个方向扩散,cnt += 1,然后我们检查当前连续走的步数是否大于k如果大于k,那么我们进入下一层判断,判断当前扩散的的点是否与当前点相等,如果相等意味着我们需要再找扩散的元素看是否相等,也就是要找到那个不一样的元素,再将cnt更新为1.如果步数小于看,意味着我们需要走相同的元素,如果扩散的元素不同就要跳过,为了防止重复访问,我们需要对步数相同的点进行跳过,并将对应的距离数组的值加一。
#include <bits/stdc++.h>
using namespace std;struct dots {int x, y, step;
};int opt[2][4] = {{-1, 1, 0, 0}, {0, 0, 1, -1}};int main() {int n, m, k;cin >> n >> m >> k;vector<vector<char>> grid(n, vector<char>(m));for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {cin >> grid[i][j];}}int vis[1005][1005][11];int dis[1005][1005][11];queue<dots> que;que.push({0, 0, 1}); // 从 (0, 0) 开始,步数为 1while (!que.empty()) {dots cur = que.front();que.pop();for (int i = 0; i < 4; i++) {int nextx = cur.x + opt[0][i];int nexty = cur.y + opt[1][i];int cur_step = cur.step + 1;if (nextx >= 0 && nextx < n && nexty >= 0 && nexty < m) {if (cur_step > k) {if (grid[cur.x][cur.y] == grid[nextx][nexty]) continue;cur_step = 1;} else {if (grid[cur.x][cur.y] != grid[nextx][nexty]) continue;}if (vis[nextx][nexty][cur_step] != 0) continue;vis[nextx][nexty][cur_step]++;dis[nextx][nexty][cur_step] = dis[cur.x][cur.y][cur.step] + 1;if (nextx == n - 1 && nexty == m - 1) {cout << dis[nextx][nexty][cur_step];return 0;}que.push({nextx, nexty, cur_step});}}}cout << -1;return 0;
}
H - 抓娃娃:
题目:
小明拿了 𝑛n 条线段练习抓娃娃。他将所有线段铺在数轴上,第 𝑖i 条线段的左端点在 𝑙𝑖li,右端点在 𝑟𝑖ri。小明用 𝑚m 个区间去框这些线段,第 𝑖i 个区间的范围是 [𝐿𝑖Li, 𝑅𝑖Ri]。如果一个线段有 至少一半 的长度被包含在某个区间内,则将其视为被这个区间框住。请计算出每个区间框住了多少个线段?
输入格式
输入共 𝑛+𝑚+1n+m+1 行。
第一行为两个正整数 𝑛n, 𝑚m。
后面 𝑛n 行,每行两个整数 𝑙𝑖li, 𝑟𝑖ri。
后面 𝑚m 行,每行两个整数 𝐿𝑖Li, 𝑅𝑖Ri。
输出格式
输出共 𝑚m 行,每行一个整数。
笔记:
这道题的思路是:我们需要使用一个有序队列存储输入的区间,由于题目中有一个特殊要求:如果一个线段有 至少一半 的长度被包含在某个区间内,则将其视为被这个区间框住,那么我们就可以推断出只要该区间中点在我们的框区间内,那么就算我们框住了这个区间,所以我们呢只需要存储所有区间的中点数据,然后我们便利我们每一次的框区间数据,遍历有序数列,找到第一个大于框左边界的中点和第一个大于框右边界的中点。
#include <bits/stdc++.h>
using namespace std;const int MAX_SIZE = 1e5 + 5; // 定义常量数组大小int main() {int n, m;cin >> n >> m;int a[MAX_SIZE]; // 正确的数组声明for (int i = 0; i < n; i++) {int l, r;cin >> l >> r;a[i] = l + r;}sort(a, a + n); // 使用指针方式排序for (int i = 0; i < m; i++) {int l, r;cin >> l >> r;l *= 2;r *= 2;int le = lower_bound(a, a + n, l) - a; // 计算左边界int ri = upper_bound(a, a + n, r) - a; // 计算右边界cout << ri - le << endl; // 输出区间内元素个数}return 0;
}