2A.Shuffle Party(枚举)
题意:
给你一个数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an。最初,每个 1 ≤ i ≤ n 1\le i\le n 1≤i≤n都有 a i = i a_i=i ai=i。
整数 k ≥ 2 k\ge2 k≥2的运算 swap ( k ) \texttt{swap}(k) swap(k)定义如下:
- 设 d d d是不等于 k k k的 k k k的最大除数 † ^\dagger †。然后交换元素 a d a_d ad和 a k a_k ak。
假设你按照这样的顺序对每一个 i = 2 , 3 , … , n i=2,3,\ldots,n i=2,3,…,n进行 swap ( i ) \texttt{swap}(i) swap(i)。找出 1 1 1在数组中的位置。换句话说,在执行这些操作后,找出 j j j和 a j = 1 a_j=1 aj=1的位置。
† ^\dagger † 如果存在一个整数 z z z使得 y = x ⋅ z y=x\cdot z y=x⋅z是 y y y的整除数,那么整数 x x x就是 y y y的整除数。
分析:
关注 1 1 1的位置可以发现规律。 1 1 1可以换到位置 2 2 2,位置 2 2 2只能换到位置 4 4 4,位置 4 4 4换到位置 8 8 8,以此类推发现换到的位置都是 2 2 2的幂次,枚举一下最大的幂次即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 30; i >= 0; i--) {
if (n >= (1 << i)) {
cout << (1 << i) << endl;
break;
}
}
}
return 0;
}
2B.Binary Path(模拟)
题意:
给你一个 2 × n 2\times n 2×n的网格,网格中充满了"0"和"1"。假设第 i i i行和第 j j j列的交叉点上的数字是 a i , j a_{i,j} ai,j。
在左上角的 ( 1 , 1 ) (1,1) (1,1)网格中有一只蚱蜢,它只能向右或向下跳一格。它想到达右下方的 ( 2 , n ) (2,n) (2,n)网格。考虑一个长度为 n + 1 n+1 n+1的二进制字符串,它由路径单元格中的数字按经过的顺序组成。
题目目标是
- 通过选择任意一条可用路径,找出字典序最小的 † ^\dagger †字符串;
- 找出能得到这个字典序最小字符串的路径数。
† ^\dagger †如果两个字符串 s s s和 t t t的长度相同,那么当且仅当在 s s s和 t t t不同的第一个位置上,字符串 s s s的元素小于 t t t中的相应元素时, s s s在词法上小于 t t t。
分析:
由于每一次只能往下和往右,所以其实我们只需要找到一个特殊点,从这个点往下走即可。如果右侧字符小于下面字符,一定向右走,这个位置记为 x x x。如果右侧字符大于下面字符,此时一定得向下走,这个位置记为 y y y。
我们在
[
x
+
1
,
y
]
[x+1,y]
[x+1,y]之间随时都可以向下走,这样得到的都是最小字典序字符串。
。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
string a, b;
cin >> a >> b;
a = ' ' + a;
b = ' ' + b;
string ans;
int y = n;
int x = 0;
for (int i = 1; i < n; i++) {
if (b[i] < a[i + 1]) {
y = i;
break;
}
if (b[i] > a[i + 1]) {
x = i;
}
}
if (y == n && x == 0) {
ans = a + b.back();
cout << ans << endl;
cout << n << endl;
} else {
ans = a.substr(1, y) + b.substr(y);
cout << ans << endl;
if (x == 0) {
cout << y << endl;
} else {
cout << y - x << endl;
}
}
}
return 0;
}
2C(1A).Bitwise Operation Wizard(数学)
题意:
本题是一个互动问题。
有一个秘密序列 p 0 , p 1 , … , p n − 1 p_0,p_1,\ldots,p_{n-1} p0,p1,…,pn−1,它是 { 0 , 1 , … , n − 1 } \{0,1,\ldots,n-1\} {0,1,…,n−1}的排列组合。
您需要找到任意两个索引 i i i和 j j j使 p i ⊕ p j p_i\oplus p_j pi⊕pj最大化。
为此,可以提出查询。每个查询的形式如下:可以选择任意的索引 a a a、 b b b、 c c c和 d d d( 0 ≤ a , b , c , d < n 0\le a,b,c,d\lt n 0≤a,b,c,d<n)。( 0 ≤ a , b , c , d < n 0\le a,b,c,d \lt n 0≤a,b,c,d<n)。接下来会计算 x = ( p a ∣ p b ) x=(p_a\mid p_b) x=(pa∣pb)和 y = ( p c ∣ p d ) y=(p_c\mid p_d) y=(pc∣pd)。最后,你会得到 x x x和 y y y的比较结果。换句话说,你会被告知是 x < y x\lt y x<y、 x > y x\gt y x>y还是 x = y x=y x=y。
请找出任意两个索引 i i i和 j j j( 0 ≤ i , j < n 0\le i,j\lt n 0≤i,j<n),使得 p i ⊕ p j p_i\oplus p_j pi⊕pj在所有这样的索引对中最大,最多可以有 3 n 3n 3n个查询。如果有多对索引满足条件,输出其中任何一个。
分析:
这道题考虑从特殊的地方入手,题目中给的数组是一个从 0 0 0到 n − 1 n-1 n−1的排列,那么最大的数显然是 n − 1 n-1 n−1,然后要找出 n − 1 n-1 n−1为 0 0 0的哪些位置为 1 1 1的数,显然两者进行 ∣ \mid ∣运算的值应该最大,但是如果仅仅是找或运算的话,那么 n − 1 n-1 n−1为 1 1 1的位置,且找到的数对应位置也为 1 1 1的位置的那种值需要排除,显然我们的目标值中 1 1 1的个数最少,目标值为 1 1 1的位置所有找到的值对应的位置应该也是 1 1 1,但是应该比目标值大。所以就要找到所有值中最小的一个。比较两个数的大小可以通过询问 a , a , b , b a,a,b,b a,a,b,b来得到。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n == 2) {
cout << "! 0 1" << endl;
} else {
char op[2];
int mx = 0, mi = 0;
for (int i = 1; i < n; i++) {
cout << "? " << mx << " " << mx << " " << i << " " << i << endl;
cin >> op;
if (op[0] == '<')
mx = i;
}
for (int i = 1; i < n; i++) {
cout << "? " << mx << " " << mi << " " << mx << " " << i << endl;
cin >> op;
if (op[0] == '<')
mi = i;
else if (op[0] == '=') {
cout << "? " << mi << " " << mi << " " << i << " " << i << endl;
cin >> op;
if (op[0] == '>')
mi = i;
}
}
cout << "! " << mx << " " << mi << endl;
}
}
return 0;
}
2D(1B).Pinball(思维)
题意:
有一个长度为 n n n的一维网格。网格的第 i i i个单元格包含一个字符 s i s_i si,该字符可以是’<‘或者’>'。
当一个弹球被放置在其中一个单元格上时,它会按照以下规则移动:
- 如果弹球位于第 i i i个单元格上,且 s i s_i si为’<‘, 弹球下一秒向左移动一个单元格。如果 s i s_i si 是’>',则向右移动一个单元格。
- 弹球移动后,字符 s i s_i si将被反转(即如果 s i s_i si以前是’<‘,将变为’>',反之亦然)。
- 当弹球离开网格时,它将停止移动:无论是从左边界还是从右边界。
本题需要回答 n n n个独立查询。在第 i i i次查询中,弹球将被放置在第 i i i个单元格中。请注意,我们总是在初始网格上放置一个弹球。
对于每个查询,计算弹球离开网格需要多少秒。可以证明,弹球总是会在有限步数内离开网格。
分析:
会改变最初放置在位置 p p p的弹球的方向的单元格是 p p p左边的’>‘和 p p p右边的’<'。
为方便起见,假设 s p s_p sp为’>'、 k = m i n ( c o u n t r i g h t ( 1 , p ) , c o u n t l e f t ( p + 1 , n ) ) k=min(countright(1,p),countleft(p+1,n)) k=min(countright(1,p),countleft(p+1,n)),弹球从左边界离开(其他情况也可以类似方法处理)。
可以通过前缀相加+二进制搜索得到 r i g h t [ 1 , … , k ] right[1,\ldots,k] right[1,…,k]和 l e f t [ 1 , … , k ] left[1,\ldots,k] left[1,…,k],其中 r i g h t right right表示 p p p左边的’>‘的单元格序列(按递减顺序), l e f t left left表示 p p p右边的’<'的单元格序列(按递增顺序)。
用 r i g h t right right和 l e f t left left来描述弹球的轨迹:
-
第一段:弹球从 r i g h t 1 right_1 right1移动到 l e f t 1 left_1 left1;
-
第二段:弹球从 l e f t 1 left_1 left1移动到 r i g h t 2 right_2 right2;
-
第三段:弹球从 r i g h t 2 right_2 right2移动到 l e f t 3 left_3 left3;
-
… \ldots …
-
第 2 k 2k 2k段:弹球从 l e f t k left_k leftk移动到左边界。
不难发现,可以使用前缀和来存储序列之和,然后快速计算弹球移动的时间。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000010;
int T, n;
ll Sl[N], Sr[N], IDl[N], IDr[N];
char s[N];
int findpre(int x) {
int L = 0, R = n + 1, M;
while (L + 1 != R) {
M = (L + R) >> 1;
if (Sr[M] < x)
L = M;
else
R = M;
}
return R;
}
int findsuf(int x) {
int L = 0, R = n + 1, M;
while (L + 1 != R) {
M = (L + R) >> 1;
if (Sl[n] - Sl[M - 1] < x) R = M;
else L = M;
}
return L;
}
int main() {
scanf_s("%d", &T);
while (T--) {
scanf_s("%d %s", &n, s);
for (int i = 1; i <= n; i++) {
Sr[i] = Sr[i - 1] + (s[i - 1] == '>');
Sl[i] = Sl[i - 1] + (s[i - 1] == '<');
IDr[i] = IDr[i - 1] + i * (s[i - 1] == '>');
IDl[i] = IDl[i - 1] + i * (s[i - 1] == '<');
}
for (int i = 1; i <= n; i++) {
if (s[i - 1] == '>') {
if (Sr[i] > Sl[n] - Sl[i]) {
int p = findpre(Sr[i] - (Sl[n] - Sl[i]));
printf_s("%lld ", 2 * ((IDl[n] - IDl[i]) - (IDr[i] - IDr[p - 1])) + i + (n + 1));
} else {
int p = findsuf((Sl[n] - Sl[i]) - Sr[i] + 1);
printf_s("%lld ", 2 * ((IDl[p] - IDl[i]) - (IDr[i] - IDr[0])) + i);
}
} else {
if (Sr[i] >= Sl[n] - Sl[i - 1]) {
int p = findpre(Sr[i] - (Sl[n] - Sl[i - 1]) + 1);
printf_s("%lld ", 2 * ((IDl[n] - IDl[i - 1]) - (IDr[i] - IDr[p - 1])) - i + (n + 1));
} else {
int p = findsuf((Sl[n] - Sl[i - 1]) - Sr[i]);
printf_s("%lld ", 2 * ((IDl[p] - IDl[i - 1]) - (IDr[i] - IDr[0])) - i);
}
}
}
printf("\n");
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。