如果你是荣誉课的学生,请你注意以下事项: 1. 每一届的题目不一样 2. 我的代码仅提供参考和复习使用,请勿直接抄袭,事后会对代码进行查重处理 3. 请更改你的OJ账户密码(如果你们的密码仍然是你们的学号的话) 4. 如果你有任何问题,可以通过博客上的方式联系我,如果有时间我会回复 5. 祝你们取得好成绩!
A. 最大子段和 题目背景 给出一个长度为 \(n\) 的整数序列 \(a_1, a_2, \cdots, a_n\) 。
你需要找一个子区间 \([l, r]\) \((1 \leq l \leq r \leq n)\) ,使得 \(\sum_{i=l}^r a_i\) 尽可能大。
你只需要输出这个最大值即可。
输入格式 第一行输入一个整数 \(n\) ,表示序列长度。
第二行有 \(n\) 个整数,\(a_1, a_2, \cdots, a_n\) 。
输出格式 输出一行,一个整数,表示答案。
样例输入 1 1 2 9 3 -1 -4 1 -5 9 -2 6 -5
样例输出 1 数据范围与提示 对于 100% 的测试数据满足 \(1 \leq n \leq 10^5,\ -10^9 \leq a_i \leq 10^9\) 。
思路和题解 一开始没思考,直接暴力用前缀和做,然后样例7爆了,时间复杂度 \(O(n^{2})\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int n; cin >> n; vector<int > a (n) ; for (int i = 0 ; i < n; i++) cin >> a[i]; vector<ll> sum (n+1 , 0 ) ; for (int i = 0 ; i < n; i++) sum[i+1 ] = sum[i] + a[i]; ll ans = LLONG_MIN; for (int i = 0 ; i < n; i++){ for (int j = i; j < n; j++){ ans = max (ans, sum[j+1 ] - sum[i]); } } cout << ans;
发现这是 \(dp\) 经典老题,改用 \(dp\) ,\(dp[i]\) 表示区间以第 \(i\) 个元素结尾的最大子区间和,得到状态转移方程 \[ dp[i+1] = max(a[i], dp[i] + a[i]) \]
1 2 3 4 5 6 7 8 9 10 11 12 int n; cin >> n; vector<int > a (n) ; for (int i = 0 ; i < n; i++) cin >> a[i]; vector<ll> dp (n+1 , 0 ) ; ll ans = LLONG_MIN; for (int i = 0 ; i < n; i++){ dp[i+1 ] = max ((ll)a[i], dp[i] + a[i]); ans = max (ans, dp[i+1 ]); } cout << ans;
B. 最小乘积 题目描述 给定四个数 \(a, b, x, y, n\) ,满足 \(a \geq x, b \geq y\) 。 你可以依次不超过 \(n\) 次操作,每次可选择如下两种方式进行操作:
使 \(a\) 减少 1,减少之后不得小于 \(x\) ; 使 \(b\) 减少 1,减少之后不得小于 \(y\) 。 你的任务是通过在这 \(n\) 次操作后,使 \(a \times b\) 尽可能小。
输入格式 输入五个整数 \(a, b, x, y, n\) 。
输出格式 输出值 \(a \times b\) 。
样例输入 1 样例输出 1 样例输入 2 样例输出 2 样例输入 3 样例输出 3 样例输入 4 1 1000000000 1000000000 1 1 1
样例输出 4 样例输入 5 1 1000000000 1000000000 1 1 1000000000
样例输出 5 样例输入 6 样例输出 6 样例输入 7 样例输出 7 数据范围 \(1 \leq a, b, x, y, n \leq 10^9\) 。
思路和题解 前两题都不喜欢动脑子导致的,暴力写了 \(\frac{1}{4}\) 的 \(dfs\) ,猛然发现这题暴力绝对会超时, \(dp\) 算上最极致的压缩空间估计也不够,于是想了想,发现这两个数和是一定的,两个数相差越远积越小,采用贪心的思想,先减到一个数不能再减为止。
后面又完善了 \(dfs\) 的解法(喜欢暴力解法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int a, b, x, y, n; cin >> a >> b >> x >> y >> n; ll ans1 = LLONG_MAX, ans2 = LLONG_MAX; { int tempa = a, tempb = b, tempn = n; int cnta = min (tempn, tempa - x); tempa -= cnta; tempn -= cnta; int cntb = min (tempn, tempb - y); tempb -= cntb; ans1 = (ll)tempa * tempb; } { int tempa = a, tempb = b, tempn = n; int cntb = min (tempn, tempb - y); tempb -= cntb; tempn -= cntb; int cnta = min (tempn, tempa - x); tempa -= cnta; ans2 = (ll)tempa * tempb; } cout << min (ans1, ans2) << endl;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ll dfs (int a, int b, int x, int y, int &n, int & cnt, ll& ans) { if (a <= x && b <= x) return ans; if (cnt == n) return ans; if (a > x){ ll temp = (ll)(--a) * b; cnt++; ans = min (ans, temp); dfs (a, b, x, y, n, cnt, ans); a++; cnt--; } if (b > y){ ll temp = (ll)a * (--b); cnt++; ans = min (ans, temp); dfs (a, b, x, y, n, cnt, ans); b++; cnt--; } return ans; } int main () { int a,b,x,y,n; cin >> a >> b >> x >> y >> n; ll ans = LLONG_MAX; int cnt = 0 ; ans = dfs (a, b, x, y, n, cnt, ans); cout << ans; return 0 ; }
C. 打牌 题目描述 假设有 \(m\) 个人,一共有 \(n \times m\) 张牌,每个人有 \(n\) 张牌,每张牌编号为正整数且两两不同,最大为 \(n \times m\) 。 每一轮大家各出一张牌,点数最大的玩家获胜得到此轮胜利,然后进入下一轮。当所有人牌打完之后,游戏结束。
现在你知道你的 \(n\) 张牌,问你最少能够获胜几轮。
输入格式 第一行两个正整数 \(m\) 和 \(n\) ,分别表示有 \(m\) 个人,每个人有 \(n\) 张牌。
接下来有 \(n\) 个整数,分别是你手中的 \(n\) 张牌上的点数。
输出格式 一个整数,表示你最少获胜的轮数。
样例输入 1 样例输出 1 样例输入 2 1 2 6 11 62 63 54 66 65 61 57 56 50 53 48
样例输出 2 数据范围 \(m \leq 20, n \leq 50\) 。
样例 1 解释:一共有 \(10\) 张牌分别为 \(1 \sim 10\) ,而你的牌为 \(1, 7, 2, 10, 9\) 。 最坏情况下,你按照 \(1, 7, 2, 10, 9\) 打出五张牌,对方按照 \(3, 8, 4, 5, 6\) 打出五张牌,那么你最少只能获胜 \(2\) 次。
思路和题解 小模拟,采用贪心的思想,考虑最坏的情况,从大到小出牌,其他人如果有大牌只有一个人出比我大的牌,其他人都出比我小的,记录输的局数,最后用总局数减去输的局数就是答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int m, n; cin >> m >> n; int tot = n * m; vector<int > v (n) ; for (int i = 0 ; i < n; ++i) cin >> v[i]; sort (v.begin (), v.end (), greater <int >()); int ans = 0 ; vector<bool > visited (tot+1 , false ) ; for (int i = 0 ; i < n; ++i) visited[v[i]] = true ; for (int i = 0 , find = 0 ; i < n; ++i, find = 0 ){ for (int j = tot; j > v[i]; --j){ if (!visited[j]){ ans++; visited[j] = true ; find = 1 ; break ; } } if (find){ for (int j = 1 , cnt = 0 ; j <= tot && cnt < m-2 ; ++j, ++cnt){ if (!visited[j]) visited[j] = true ; } } else { for (int j = 1 , cnt = 0 ; j <= tot && cnt < m-1 ; ++j, ++cnt) { if (!visited[j]) visited[j] = true ; } } } cout << n-ans;
D. 看番 题目描述 众所周知,ZZR 住在 B 站。
这一天,L 老师来找 ZZR 玩,ZZR 决定向 L 老师展现一下自己快速看器的本领。
ZZR 让 L 老师任意挑选了一部器,L 老师一眼看中了《辉夜大小姐想让我告白》,已知这部器一共有 \(N\) 个精彩片段,每个精彩片段用 \((L_i, R_i)\) 来表示。
ZZR 从第 1 分钟开始看,每次可以按下快进键,快进 \(X\) 分钟,但 ZZR 不想因为快进而错过任何精彩片段,问 ZZR 最快需要多久可以看到这部器。
输入格式 第一行给出两个整数 \(N\) 和 \(X\) 。
接下来 \(N\) 行,每行给出两个整数 \(L_i\) 和 \(R_i\) ,保证 \(R_{i-1} < L_i\) 。
输出格式 输出一个整数,表示 ZZR 最快看完这部器的时间。
样例输入 1 样例输出 1 样例输入 2 样例输出 2 数据范围 \(1 \leq N \leq 50, 1 \leq X \leq 10^5, 1 \leq L_i \leq R_i \leq 10^5\)
在第一个样例中,ZZR 从第 1 分钟开始看,由于 \([1, 3]\) 区间内没有精彩片段,因此 ZZR 快进到了第 4 分钟。
观看完 \([4, 6]\) 区间内的内容后,时间来到了第 7 分钟,由于 \([7, 9]\) 区间内没有精彩片段,因此 ZZR 快进到了第 10 分钟。
观看完 \([10, 12]\) 区间内的内容后,看器结束。
一共耗时 \(3 + 3 = 6\) 分钟。
思路与题解 简单题,每个空闲时间段能跳就跳,遍历一遍即可
1 2 3 4 5 6 7 8 9 10 11 12 13 int n,x; cin >> n >> x;; int l,r; int ans = 0 ; int lastr = 0 ; for (int i = 0 ; i < n; i++){ cin >> l >> r; ans += (l - lastr) % x; ans += r - l; lastr = r; } cout << ans; return 0 ;
E.SA. 2进制高精度乘法 题目背景 输入两个很大的整数,你需要输出它们的乘积。
特别的,你需要完成 2 进制下的这个问题,而非我们常用的 10 进制。
输入格式 第一行输入一个二进制串,保证最高位为 1。
第二行输入一个二进制串,保证最高位为 1。
输出格式 输出一行,一个二进制串,表示输入的两个二进制串相乘后的答案。
样例输入 1 样例输出 1 样例输入 2 样例输出 2 数据范围与提示 对于 100% 的测试数据满足输入的二进制串长度不超过 5000。
思路与题解 经典模板题,这里采用数组的思想解(链表也是一样的),先逆向,选一个位数少的做被乘数,列竖式计算,空位补零,注意加法进位,最后再逆向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 string add (string &a, string &b) { int n = a.size (); int m = b.size (); string maxnum = n > m ? a : b; string minnum = n > m ? b : a; string ans (maxnum.size(), '0' ) ; int carry = 0 ; for (int i = 0 ; i < minnum.size ();++i){ ans[i] = (char )(((minnum[i] - '0' ) ^ (maxnum[i] - '0' ) ^ carry) + '0' ); carry = (minnum[i] - '0' ) + (maxnum[i] - '0' ) + carry > 1 ? 1 : 0 ; } for (int i = minnum.size (); i < maxnum.size (); ++i){ ans[i] = (char )(((maxnum[i] - '0' ) ^ carry) + '0' ); carry = (maxnum[i] - '0' ) + carry > 1 ? 1 : 0 ; } if (carry == 1 ) ans.push_back ('1' ); return ans; } int main () { string str1, str2; cin >> str1 >> str2; reverse (str1. begin (), str1. end ()); reverse (str2. begin (), str2. end ()); int n = str1. size (); int m = str2. size (); string ans; for (int i = 0 ; i < m; ++i){ string temp; if (str2[i] == '0' ){ temp.resize (n,'0' ); } else { temp = str1; } for (int j = 0 ; j < i; ++j){ temp.insert (temp.begin (), '0' ); } ans = add (ans, temp); } reverse (ans.begin (), ans.end ()); for (auto c : ans) cout << c; return 0 ; }
SB. 1·2·3=6 题目背景 某一天,George在12306抢票的时候想到了这么一个问题:
给出一个长度为 \(n\) 的排列 \(p_1, p_2, \cdots, p_n\) 。
(\(1 \leq p_i \leq n\) ,且 \(1,2,\cdots,n\) 每个数字出现恰好一次)
你需要统计满足条件的四元组 \((a, b, c, d)\) 的数量。
合法四元组的条件为:
\[1 \leq a < b < c < d \leq n \tag{1}\]
且 \(p_a < p_b < p_c < p_d \tag{2}\)
且 \(p_a \cdot p_b \cdot p_c = p_d \tag{3}\)
输入格式 第一行,一个整数 \(n\) 。
第二行,\(n\) 个整数,保证为一个排列。
输出格式 输出一个整数,表示合法四元组的数量。
样例输入 #1 样例输出 #1 样例输入 #2 样例输出 #2 数据范围与提示 对于 100% 的测试数据满足 \(n \leq 10^6\) 。
提示:通常 cin/cout 的输入/输出速度慢于 scanf/printf,尤其对于 \(10^6\) 或更大输入/输出级别来说,差异会更加明显。如果要提升 cin/cout 的速度,可以采用关闭同步的方法(关闭后,cin/cout 不能和其它输入/输出函数混用):
1 2 3 4 5 6 7 8 #include <iostream> using namespace std;int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); }
思路与题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <bits/stdc++.h> #include <unordered_map> using namespace std;int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); int n; cin >> n; unordered_map<int , int > pos; for (int i = 0 ; i < n; ++i) { int val; cin >> val; pos[val] = i; } long long count = 0 ; for (int x = 1 ; x <= n; ++x) { if ((long long )x * x * x > n) { break ; } for (int y = x + 1 ; y <= n; ++y) { if ((long long )x * y * y > n) { break ; } for (int z = y + 1 ; z <= n; ++z) { long long w = (long long )x * y * z; if (w > n) { break ; } if (pos.at (x) < pos.at (y) && pos.at (y) < pos.at (z) && pos.at (z) < pos.at (w)) { count++; } } } } cout << count << endl; return 0 ; }
C. 拍照 题目背景 某个班级有 \(n\) 位同学,每个同学都有一个唯一的学号,分别是 \(p_1, p_2, \cdots, p_n\ (1 \leq p_i \leq n)\) 。
除此之外,每个同学有一个整数身高,分别为 \(h_1, h_2, \cdots, h_n\) 。
也就是说,第 \(i\) 个同学的学号是 \(p_i\) ,身高是 \(h_i\) 。
现在,一共要拍 \(n\) 次照片。
第 \(1\) 次拍照,只有学号为 \(p_1\) 的同学参加。 第 \(2\) 次拍照,学号为 \(p_1, p_2\) 的同学参加。 第 \(3\) 次拍照,学号为 \(p_1, p_2, p_3\) 的同学参加。 …… 第 \(n\) 次拍照,所有同学参加。 每次拍照的时候,参与拍照的同学会按照学号从小到大的顺序排成一排。
每次拍照的照片有一个美观度,定义为相邻两个同学身高差的平方和。例如,假设排成一排的同学从左到右的身高依次为 \((1, 4, 1, 2)\) ,则美观度是 \((1-4)^2+(4-1)^2+(1-2)^2=19\) 。
现在,老师希望你算出这 \(n\) 次拍照的照片的美观度分别是多少。
输入格式 第一行,一个整数 \(n\) ,表示班级人数。
第二行,\(n\) 个整数 \(p_1, p_2, \cdots, p_n\) ,表示同学的学号。
第三行,\(n\) 个整数 \(h_1, h_2, \cdots, h_n\) ,表示同学的身高。
输出格式 输出 \(n\) 行,每行一个整数,第 \(i\) 行表示第 \(i\) 次拍照的照片美观度。
样例输入 1 样例输出 1 数据范围与提示 对于 100% 的测试数据满足 \(n \leq 10^6,\ 1 \leq p_i \leq n,\ 1 \leq h_i \leq 10^6\) 。
(输入输出量比较大,请选择较快的输入输出方式)
思路与题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;long long square (long long x) { return x * x; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); int n; cin >> n; vector<int > p (n) ; vector<int > h (n) ; vector<int > h_by_id (n + 1 ) ; for (int i = 0 ; i < n; ++i) cin >> p[i]; for (int i = 0 ; i < n; ++i) cin >> h[i]; for (int i = 0 ; i < n; ++i) h_by_id[p[i]] = h[i]; map<int , int > students; long long ans = 0 ; for (int i = 0 ; i < n; ++i) { int newId = p[i]; int newH = h_by_id[newId]; students[newId] = newH; auto it = students.find (newId); auto prevIt = (it == students.begin ()) ? students.end () : prev (it); auto next_it = next (it); if (prevIt != students.end () && next_it != students.end ()) { ans -= square (prevIt->second - next_it->second); ans += square (prevIt->second - newH); ans += square (newH - next_it->second); } else if (prevIt != students.end ()) { ans += square (prevIt->second - newH); } else if (next_it != students.end ()) { ans += square (newH - next_it->second); } cout << ans << "\n" ; } return 0 ; }