A.Maximize?(枚举)
题意:
给你一个整数 x x x。你的任务是找出任意一个整数 y y y ( 1 ≤ y < x ) (1\le y\lt x) (1≤y<x),使得 gcd ( x , y ) + y \gcd(x,y)+y gcd(x,y)+y为最大可能数。 ( 1 ≤ y < x ) (1\le y\lt x) (1≤y<x)使得 gcd ( x , y ) + y \gcd(x,y)+y gcd(x,y)+y最大。
注意,如果有一个以上的 y y y满足该语句,允许找到任何一个。
gcd ( a , b ) \gcd(a,b) gcd(a,b)是 a a a和 b b b的最大公约数。例如, gcd ( 6 , 4 ) = 2 \gcd(6,4)=2 gcd(6,4)=2。
分析:
本题数据范围较小,直接暴力枚举即可。
此外,因为 y < x y\lt x y<x所以 gcd ( x , y ) \gcd(x,y) gcd(x,y)等价于 gcd ( x , x − y ) \gcd(x,x-y) gcd(x,x−y),易得 gcd ( x , x − y ) ≤ x − y \gcd(x,x-y)\le x-y gcd(x,x−y)≤x−y,所以 gcd ( x , x − y ) + y ≤ x \gcd(x,x-y)+y\le x gcd(x,x−y)+y≤x,又因为相邻两个数的 gcd \gcd gcd为 1 1 1,所以当 y = x − 1 y=x-1 y=x−1时一定满足题目,可以直接输出 x − 1 x-1 x−1。
代码:
#include<bits/stdc++.h>using namespace std;int gcd(int a, int b) {if (b > a) swap(a, b);if (b == 0) return a;return gcd(b, a % b);
}int main() {int t;cin >> t;while (t--) {int x;cin >> x;int res = 0;int ans = 1;for (int y = 1; y < x; y++) {if (gcd(x, y) + y > res) {res = gcd(x, y) + y;ans = y;}}cout << ans << endl;}return 0;
}
B.Prefiquence(双指针)
题意:
给你两个二进制字符串 a a a和 b b b。二进制字符串是由字符"0"和"1"组成的字符串。
你的任务是确定最大可能的数字 k k k,使得长度为 k k k的字符串 a a a的前缀是字符串 b b b的子序列。
如果 a a a可以从 b b b中删除几个(可能是零个或全部)元素,那么序列 a a a就是序列 b b b的子序列。
分析:
本题本质就是找 b b b字符串的子序列作为 a a a的前缀最大是多少。
考虑双指针,设置 l l l指针指向 a a a字符串的第一个字母 r r r指针指向 b b b字符串的第一个字母,若 l l l和 r r r指向的字母相同,则双指针往后移动,并更新 a n s ans ans值为 l l l指针 若 l l l和 r r r不相同 则移动 r r r指针至第一个可以和 l l l指针匹配的位置,逐个移动直到 l l l或者 r r r指针跑到 n n n或 m m m。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;void solve() {LL n, m;cin >> n >> m;string a, b;cin >> a >> b;a = " " + a;b = " " + b;LL l, r;LL ans = 0;l = r = 1;while (l <= n && r <= m) {if (a[l] == b[r]) {l++;r++;ans = l;} else if (a[l] != b[r]) {while (a[l] != b[r] && r <= m) {r++;}}}ans = max(ans - 1, 0LL);cout << ans << endl;
}int main() {LL t;cin >> t;while (t--)solve();return 0;
}
C.Assembly via Remainders(构造)
题意:
给你一个数组 x 2 , x 3 , … , x n x_2,x_3,\dots,x_n x2,x3,…,xn。你的任务是找出任意一个数组 a 1 , … , a n a_1,\dots,a_n a1,…,an,其中:
- 对于所有的 1 ≤ i ≤ n 1\le i\le n 1≤i≤n, 1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1≤ai≤109。
- 对于所有 2 ≤ i ≤ n 2\le i\le n 2≤i≤n, x i = a i m o d a i − 1 x_i=a_i\bmod a_{i-1} xi=aimodai−1。
这里的 c m o d d c\bmod d cmodd表示整数 c c c除以整数 d d d的余数。例如, 5 m o d 2 = 1 5\bmod 2=1 5mod2=1, 72 m o d 3 = 0 72\bmod 3=0 72mod3=0, 143 m o d 14 = 3 143\bmod 14=3 143mod14=3。
注意,如果有一个以上的 a a a满足该语句的要求,你可以找到任何一个。
分析:
题目要求构造 a i a_i ai,考虑如果 a i − 1 a_{i-1} ai−1比 a i a_i ai大的话,根据模的计算方法,可以直接放入 x i x_i xi。
我们每次构造出的 a i a_i ai,都让它等于 a i − 1 + x i a_{i-1}+x_i ai−1+xi,让数组 a a a一直递增,即可符合要求。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;
const LL N = 505;
int x[N], a[N];void solve() {int n;cin >> n;for (int i = 2; i <= n; i++)cin >> x[i];a[n] = 1e9;for (int i = n; i >= 2; i--) {if (x[i] == a[i]) {a[i - 1] = 1e9;continue;}a[i - 1] = a[i] - x[i];}for (int i = 1; i <= n; i++) {cout << a[i] << " ";}cout << endl;
}int main() {int t;cin >> t;while (t--) {solve();}return 0;
}
D.Permutation Game(贪心)
题意:
Bodya和Sasha发现了一个排列 p 1 , … , p n p_1,\dots,p_n p1,…,pn和一个数组 a 1 , … , a n a_1,\dots,a_n a1,…,an。他们决定玩一个著名的 “排列游戏”。
长度为 n n n的排列是由 n n n个不同的整数组成的数组,这些整数从 1 1 1到 n n n按任意顺序排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4]是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2]不是一个排列( 2 2 2在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4]也不是一个排列( n = 3 n=3 n=3,但数组中有 4 4 4)。
它们都在排列中选择了一个起始位置。
对局持续了 k k k个回合。棋手同时下棋。在每个回合中,每个棋手都会发生两件事:
- 如果棋手当前的位置是 x x x,他的得分就会增加 a x a_x ax。
- 然后棋手要么停留在当前位置 x x x,要么从 x x x移动到 p x p_x px。
在整整 k k k个回合后,得分较高的一方即为获胜者。
知道了Bodya的起始位置 P B P_B PB和Sasha的起始位置 P S P_S PS后,如果两位棋手都想获胜,那么谁会赢得对局?
分析:
最优的方案一定是先走几步,然后一直停在某个点,如果我们站的点是最大的,那么我们就不需要移动,因为可以一直站在上面得到最大的分数。
否则我们就去下一个位置去看看,能不能获得更多的得分。
遍历的话因为是排列,移动是一个环,所以最多的遍历次数是 n n n。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;void solve() {LL n, k, pb, ps;cin >> n >> k >> pb >> ps;vector<LL> p(n + 1), a(n + 1);for (int i = 1; i <= n; i++)cin >> p[i];for (int i = 1; i <= n; i++)cin >> a[i];LL ans_pb = 0, ans_ps = 0;LL res_pb = 0, res_ps = 0;for (int i = 0; i < min(k, n); i++) {res_pb += a[pb];ans_pb = max(ans_pb, res_pb + (k - i - 1) * a[pb]);pb = p[pb];res_ps += a[ps];ans_ps = max(ans_ps, res_ps + (k - i - 1) * a[ps]);ps = p[ps];}if (ans_pb > ans_ps)cout << "Bodya" << endl;else if (ans_pb == ans_ps)cout << "Draw" << endl;elsecout << "Sasha" << endl;
}int main() {int t;cin >> t;while (t--) {solve();}return 0;
}
E.Cells Arrangement(构造)
题意:
给你一个整数 n n n。您在网格 n × n n\times n n×n中选择 n n n个单元格 ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) (x_1,y_1),(x_2,y_2),\dots,(x_n,y_n) (x1,y1),(x2,y2),…,(xn,yn),其中 1 ≤ x i ≤ n 1\le x_i\le n 1≤xi≤n和 1 ≤ y i ≤ n 1\le y_i\le n 1≤yi≤n。
设 H \mathcal{H} H为任意一对单元格之间不同的曼哈顿距离集合。你的任务是最大化 H \mathcal{H} H的大小。注释中给出了集合及其构造的例子。
如果存在不止一个解,你可以输出任意一个。
单元格 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)和 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)之间的曼哈顿距离等于 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| ∣x1−x2∣+∣y1−y2∣。
分析:
观察样例发现,在 ( 1 , 1 ) (1,1) (1,1)放一个,然后 ( 1 , 2 ) (1,2) (1,2)下面放一个,如果有多的,全部斜着放是能取完所有的距离情况的,这样放是最大值。
代码:
#include<bits/stdc++.h>using namespace std;void solve() {int n;cin >> n;cout << "1 1" << endl;cout << "1 2" << endl;for (int i = 3; i <= n; i++) {cout << i << " " << i << endl;}
}int main() {int t;cin >> t;while (t--) {solve();}return 0;
}
F.Equal XOR Segments(前缀处理)
题意:
如果可以将数组分成 k > 1 k\gt 1 k>1部分,使得每一部分值按位异或都相等,那么我们就称这个数组为 x 1 , … , x m x_1,\dots,x_m x1,…,xm有趣。
更正式地说,你必须把数组 x x x分成 k k k个连续的部分, x x x中的每个元素都必须准确地属于 1 1 1部分。设 y 1 , … , y k y_1,\dots,y_k y1,…,yk分别是各部分元素的XOR。那么 y 1 = y 2 = ⋯ = y k y_1=y_2=\dots=y_k y1=y2=⋯=yk必须满足。
例如,如果是 x = [ 1 , 1 , 2 , 3 , 0 ] x=[1,1,2,3,0] x=[1,1,2,3,0],可以将其拆分如下: [ 1 ] , [ 1 ] , [ 2 , 3 , 0 ] [\color{blue}1],[\color{green}1],[\color{red}2,\color{red}3,\color{red}0] [1],[1],[2,3,0]。事实上是 1 = 1 = 2 ⊕ 3 ⊕ 0 \color{blue}1=\color{green}1=\color{red}2\oplus\color{red}3\oplus\color{red}0 1=1=2⊕3⊕0。
给你一个数组 a 1 , … , a n a_1,\dots,a_n a1,…,an。你的任务是回答 q q q次查询:
- 对于固定的 l l l, r r r, 判断子数组 a l , a l + 1 , … , a r a_l,a_{l+1},\dots,a_r al,al+1,…,ar是否有趣。
分析:
区间问题先转化为前缀异或。
- S r ⊕ S l − 1 = 0 S_r\oplus S_{l-1}=0 Sr⊕Sl−1=0
题目保证了 l < r l\lt r l<r,所以一定有解。
- S r ⊕ S l − 1 = v S_r\oplus S_{l-1}=v Sr⊕Sl−1=v
区间一定可以划分为奇数段。如果能划分成奇数段,则一定能划分成 3 3 3段。
最终划分的异或值一定是 v v v。
于是问题转变为是否能把 [ l , r ] [l,r] [l,r]分成三段,使得每段异或值都为 v v v。
找到最大的 i < r i\lt r i<r使得 S r ⊕ S i = v S_r\oplus S_i=v Sr⊕Si=v,如果找不到则无解。
找到最大的 j < i j\lt i j<i使得 S i ⊕ S j = v S_i\oplus S_j=v Si⊕Sj=v,如果找不到或则 j < l − 1 j\lt l-1 j<l−1无解。
具体实现可以维护一个map<int, vector<int> >
来实现。
代码:
#include<bits/stdc++.h>typedef long long LL;
using namespace std;void solve() {LL n, q;cin >> n >> q;vector<LL> a(n + 1);map<int, vector<int>> mp;for (int i = 1; i <= n; i++)cin >> a[i];for (int i = 1; i <= n; i++) {a[i] ^= a[i - 1];mp[a[i]].push_back(i);}while (q--) {int l, r;cin >> l >> r;LL p = a[r] ^ a[l - 1];if (p == 0) {cout << "YES" << endl;} else {auto &v1 = mp[a[r]];auto pl = lower_bound(v1.begin(), v1.end(), l);if (pl == v1.end() || *pl >= r) {cout << "NO" << endl;continue;}LL posl = *pl;auto &v2 = mp[a[l - 1]];auto pr = lower_bound(v2.begin(), v2.end(), posl + 1);if (pr != v2.end() && *pr < r) {cout << "YES" << endl;} elsecout << "NO" << endl;}}
}int main() {int T;cin >> T;while (T--) {solve();}return 0;
}
G1.Division + LCP (easy version)(LCP)
题意:
这是问题的简单版本。在这个版本中 l = r l=r l=r
给你一个字符串 s s s。对于固定的 k k k,考虑将 s s s恰好分成 k k k个连续的子串 w 1 , … , w k w_1,\dots,w_k w1,…,wk。假设 f k f_k fk是所有分割中最大的 L C P ( w 1 , … , w k ) LCP(w_1,\dots,w_k) LCP(w1,…,wk)。
L C P ( w 1 , … , w m ) LCP(w_1,\dots,w_m) LCP(w1,…,wm)是字符串 w 1 , … , w m w_1,\dots,w_m w1,…,wm的最长公共前缀的长度。
例如,如果 s = a b a b a b c a b s=abababcab s=abababcab和 k = 4 k=4 k=4,可能的除法是 a b a b a b c a b \color{red}{ab}\color{blue}{ab}\color{orange}{abc}\color{green}{ab} abababcab。由于 a b ab ab是这四个字符串的最长公共前缀,因此 L C P ( a b , a b , a b c , a b ) LCP(\color{red}{ab},\color{blue}{ab},\color{orange}{abc},\color{green}{ab}) LCP(ab,ab,abc,ab)就是 2 2 2。请注意,每个子串都由一段连续的字符组成,每个字符都属于一个子串。
您的任务是找出 f l , f l + 1 , … , f r f_l,f_{l+1},\dots,f_r fl,fl+1,…,fr。在此版本中为 l = r l=r l=r。
分析:
如果存在划分使得 L C P LCP LCP为 v v v,则任意 v ‘ < v v \text{`} \lt v v‘<v都存在划分。
存在单调性,可以二分前缀长度。
令前缀字符串为 t t t, s s s种最多有 x x x个不相交的子串 t t t。
如果 x ≥ k x\ge k x≥k,则存在划分使得 L C P LCP LCP为 t t t。
可以用 k m p kmp kmp或字符串哈希求出。
代码:
#include<bits/stdc++.h>using namespace std;void solve() {int n, _, k;string s;cin >> n >> _ >> k >> s;vector<int> ne(n + 1, 0);ne[0] = -1;for (int i = 1, j = -1; i < n; i++) {while (j >= 0 && s[j + 1] != s[i])j = ne[j];if (s[j + 1] == s[i])j++;ne[i] = j;}auto check = [&](int m) -> bool {if (m == 0)return 1;string t = s.substr(0, m);int tmp = 0;for (int i = 0, j = -1; i < n; i++) {while (j != -1 && s[i] != t[j + 1])j = ne[j];if (s[i] == t[j + 1])j++;if (j == m - 1) {tmp++;j = -1;}}return tmp >= k;};int l = 0, r = n / k;while (l < r) {int mid = l + r + 1 >> 1;if (check(mid))l = mid;elser = mid - 1;}cout << l << endl;
}int main() {int t;cin >> t;while (t--) {solve();}return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。