A.Problem Generator(遍历)
题意:
弗拉德计划在下个月举行 m m m轮比赛。每轮比赛应包含一个难度为"A"、“B”、“C”、“D”、“E”、"F"和"G"的问题。
弗拉德已经有了一个 n n n个问题的问题库,其中第 i i i个问题的难度为 a i a_i ai。这些问题可能不够多,所以他可能需要再想出一些问题。
弗拉德想要尽可能少地提出问题,所以他要求你找出他需要提出的问题的最小数量,以便举行 m m m轮比赛。
例如,如果 m = 1 m=1 m=1、 n = 10 n=10 n=10、 a = a= a='BGECDCBDEDBGECDCBDED",那么他需要提出两道难题:一道难度为"A",一道难度为"F"。
分析:
每个比赛都应该包含"A"、“B”、“C”、“D”、“E”、"F"和"G"的问题。遍历数据,暴力模拟统计需要加入的题目即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;
int T, n, m;
char a[N];
map<char, int> k;
int main() {
cin >> T;
while (T--) {
k.clear();
cin >> n >> m;
cin >> a + 1;
for (int i = 1; i <= n; i++) {
k[a[i]]++;
}
int cnt = 0;
for (char i = 'A'; i <= 'G'; i++) {
cnt += max(0, m - k[i]);
}
cout << cnt << endl;
}
return 0;
}
B.Choosing Cubes(思维)
题意:
德米特里有 n n n个立方体,从左到右编号为 1 1 1至 n n n。索引为 f f f的立方体是他的最爱。
德米特里把所有的立方体都扔到了桌子上,第 i i i个立方体显示了值 a i a_i ai( 1 ≤ a i ≤ 100 1\le a_i\le 100 1≤ai≤100)。之后,他按照数值从大到小的非递增顺序排列这些立方体。如果两个立方体的数值相同,它们可以按照任意顺序排列。
排序后,德米特里取出了第一个 k k k立方体。然后,他开始关注自己是否取出了最喜欢的立方体(注意,排序后立方体的位置可能会发生变化)。
例如,如果 n = 5 n=5 n=5、 f = 2 f=2 f=2、 a = [ 4 , 3 , 3 , 2 , 3 ] a=[4,\color{green}3,3,2,3] a=[4,3,3,2,3](最喜欢的立方体用绿色标出)和 k = 2 k=2 k=2,可能会发生以下情况:
- 在对 a = [ 4 , 3 , 3 , 3 , 2 ] a=[4,\color{green}3,3,3,2] a=[4,3,3,3,2]排序后,由于最喜爱的立方体最终排在了第二位,因此它将被移除。
- 在对 a = [ 4 , 3 , 3 , 3 , 2 ] a=[4,3,\color{green}3,3,2] a=[4,3,3,3,2]排序后,由于最喜爱的魔方排在了第三位,因此它不会被移除。
分析:
我们只需要检查排完序之后比较在 k k k位置的立方体 a k a_k ak与最喜欢的立方体 a f a_f af的大小即可,如果 a k > a f a_k\gt a_f ak>af,说明 a f a_f af的位置在 k k k之前,一定会被删;如果 a k < a f a_k\lt a_f ak<af,说明 a f a_f af的位置在 k k k之后,一定不会被删;如果 a k = a f a_k=a_f ak=af,检查 a k + 1 a_{k+1} ak+1,如果 a k + 1 = a f a_{k+1}=a_f ak+1=af,则有可能被删,否则一定被删。
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n, f, k;
cin >> n >> f >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int fa = a[f];
sort(a.begin() + 1, a.end(), greater<int>());
if (k == n)
cout << "YES" << endl;
else if (fa > a[k])
cout << "YES" << endl;
else if (fa < a[k])
cout << "NO" << endl;
else {
if (a[k + 1] == fa)
cout << "MAYBE" << endl;
else
cout << "YES" << endl;
}
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
C.Sofia and the Lost Operations(思维)
题意:
索菲亚有一个由 n n n个整数 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an组成的数组。有一天,她对这个数组感到厌倦,于是决定依次对它进行 m m m次修改操作。
每个修改操作都由一对数字 ⟨ c j , d j ⟩ \langle c_j,d_j\rangle ⟨cj,dj⟩来描述,这意味着数组中索引为 c j c_j cj的元素应赋值为 d j d_j dj,即执行赋值 a c j = d j a_{c_j}=d_j acj=dj。依次执行所有修改操作后,索菲亚丢弃了得到的数组。
最近,你发现了一个由 n n n个整数组成的数组 b 1 , b 2 , … , b n b_1,b_2,\ldots,b_n b1,b2,…,bn。你想知道这个数组是否是索菲亚的数组。你知道原始数组的值以及 d 1 , d 2 , … , d m d_1,d_2,\ldots,d_m d1,d2,…,dm的值。结果发现数值 c 1 , c 2 , … , c m c_1,c_2,\ldots,c_m c1,c2,…,cm丢失了。
是否存在一个序列 c 1 , c 2 , … , c m c_1,c_2,\ldots,c_m c1,c2,…,cm使得对数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an的修改操作 ⟨ c 1 , d 1 , ⟩ , ⟨ c 2 , d 2 , ⟩ , … , ⟨ c m , d m ⟩ \langle c_1,d_1,\rangle,\langle c_2,d_2,\rangle,\ldots,\langle c_m,d_m\rangle ⟨c1,d1,⟩,⟨c2,d2,⟩,…,⟨cm,dm⟩的顺序应用将其转换为数组 b 1 , b 2 , … , b n b_1,b_2,\ldots,b_n b1,b2,…,bn?
分析:
若最后一个修改数存在于 b b b数组中,那么只需要看这些修改的数能否把 a a a数组中待修改的数全部覆盖即可,否则一定不存在。
所以判断最后一个是否合法,前面的能否一一匹配即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int MOD = 998244353;
int n, a[N], m, b[N], c[N];
map<int, int> apper, be;
void solve() {
apper.clear();
be.clear();
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
apper[b[i]]++;
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> c[i];
be[c[i]]++;
}
if (apper.find(c[m]) == apper.end())
cout << "no" << endl;
else {
bool flag = 1;
for (int i = 1; i <= n; i++) {
if (a[i] != b[i]) {
if (!be[b[i]]) {
flag = 0;
break;
}
be[b[i]]--;
}
}
if (flag)
cout << "yes" << endl;
else
cout << "no" << endl;
}
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D.GCD-sequence(模拟)
题意:
两个整数 x x x和 y y y的GCD(最大公约数)是 z z z可以整除 x x x和 y y y的最大整数。例如, G C D ( 36 , 48 ) = 12 GCD(36,48)=12 GCD(36,48)=12、 G C D ( 5 , 10 ) = 5 GCD(5,10)=5 GCD(5,10)=5和 G C D ( 7 , 11 ) = 1 GCD(7,11)=1 GCD(7,11)=1。
克里斯蒂娜有一个数组 a a a,其中正好包含 n n n个正整数。她想通过计算每一对相邻数字的GCD得到一个新数组 b b b,称为GCD序列。
因此,GCD序列 b b b中的元素将使用公式 b i = G C D ( a i , a i + 1 ) b_i=GCD(a_i,a_{i+1}) bi=GCD(ai,ai+1)计算 1 ≤ i ≤ n − 1 1\le i\le n-1 1≤i≤n−1。
确定是否有可能从数组 a a a中恰好删除一个数,从而使GCD序列 b b b不递减(即 b i ≤ b i + 1 b_i\le b_{i+1} bi≤bi+1始终为真)。
例如,假设克里斯蒂娜有一个数组 a a a=[ 20 , 6 , 12 , 3 , 48 , 36 20,6,12,3,48,36 20,6,12,3,48,36]。如果她从中取出 a 4 = 3 a_4=3 a4=3并计算 b b b的GCD序列,她会得到:
- b 1 = G C D ( 20 , 6 ) = 2 b_1=GCD(20,6)=2 b1=GCD(20,6)=2
- b 2 = G C D ( 6 , 12 ) = 6 b_2=GCD(6,12)=6 b2=GCD(6,12)=6
- b 3 = G C D ( 12 , 48 ) = 12 b_3=GCD(12,48)=12 b3=GCD(12,48)=12
- b 4 = G C D ( 48 , 36 ) = 12 b_4=GCD(48,36)=12 b4=GCD(48,36)=12
得到的GCD序列 b b b=[ 2 , 6 , 12 , 12 2,6,12,12 2,6,12,12]是非递减的,因为 b 1 ≤ b 2 ≤ b 3 ≤ b 4 b_1\le b_2\le b_3\le b_4 b1≤b2≤b3≤b4。
分析:
读题发现是大模拟,判断前驱合法,后继合法,拼接即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 2e5 + 10;
const LL MAXN = 0x3f3f3f3f;
int n, a[N];
int gcd1[N];
bool pre[N], suc[N];
map<int, int> apper, be;
LL _gcd(LL a, LL b) {
return b > 0 ? _gcd(b, a % b) : a;
}
void solve() {
apper.clear();
be.clear();
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (i > 1)
gcd1[i] = _gcd(a[i], a[i - 1]);
}
gcd1[0] = gcd1[1] = 0;
gcd1[n + 1] = MAXN;
pre[1] = pre[2] = true;
for (int i = 3; i <= n; i++) {
pre[i] = pre[i - 1] && (gcd1[i] >= gcd1[i - 1]);
}
suc[n] = suc[n - 1] = true;
for (int i = n - 2; i >= 1; i--) {
suc[i] = suc[i + 1] && (gcd1[i + 1] <= gcd1[i + 2]);
}
bool flag = 0;
for (int i = 2; i < n; i++) {
int g = (__gcd(a[i - 1], a[i + 1]));
if (pre[i - 1] && suc[i + 1] && g >= gcd1[i - 1] && g <= gcd1[i + 2]) {
flag = 1;
break;
}
}
if (flag || pre[n - 1] || suc[2])
cout << "Yes" << endl;
else
cout << "No" << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E.Permutation of Rows and Columns(遍历)
题意:
给你一个大小为 n n n乘 m m m的矩阵 a a a,其中包含从 1 1 1到 n ⋅ m n\cdot m n⋅m的整数排列。
一个由 n n n个整数组成的排列是一个数组,其中包含了从 1 1 1到 n n n的所有数字,而这些数字恰好出现过一次。例如,数组 [ 1 ] [1] [1]、 [ 2 , 1 , 3 ] [2,1,3] [2,1,3]、 [ 5 , 4 , 3 , 2 , 1 ] [5,4,3,2,1] [5,4,3,2,1]是排列,而数组 [ 1 , 1 ] [1,1] [1,1]、 [ 100 ] [100] [100]、 [ 1 , 2 , 4 , 5 ] [1,2,4,5] [1,2,4,5]不是排列。
如果一个矩阵的所有元素都写出后,得到的数组是一个排列,那么这个矩阵就包含一个排列。矩阵 [ [ 1 , 2 ] , [ 3 , 4 ] ] [[1,2],[3,4]] [[1,2],[3,4]], [ [ 1 ] ] [[1]] [[1]], [ [ 1 , 5 , 3 ] , [ 2 , 6 , 4 ] ] [[1,5,3],[2,6,4]] [[1,5,3],[2,6,4]]包含排列,而矩阵 [ [ 2 ] ] [[2]] [[2]], [ [ 1 , 1 ] , [ 2 , 2 ] ] [[1,1],[2,2]] [[1,1],[2,2]], [ [ 1 , 2 ] , [ 100 , 200 ] ] [[1,2],[100,200]] [[1,2],[100,200]]不包含排列。
您可以在一次操作中执行以下两个操作之一:
- 选择列 c c c和 d d d( 1 ≤ c , d ≤ m 1\le c,d\le m 1≤c,d≤m, c ≠ d c\ne d c=d)并交换这些列;
- 选择行 c c c和 d d d( 1 ≤ c , d ≤ n 1\le c,d\le n 1≤c,d≤n, c ≠ d c\ne d c=d),并交换这些行。
您可以执行任意数量的操作。
给你原始矩阵 a a a和矩阵 b b b。您的任务是确定是否可以通过给定的运算将矩阵 a a a变换为矩阵 b b b。
分析:
很容易就可以观察到,无论怎么进行交换,每一行拥有的数字都不会变,只是位置改变了,对于每一列同样也是如此。只需要逐行和逐列的去检查 b b b矩阵的一行或一列对应的数字集合是否在 a a a中出现即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
void solve() {
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m));
vector<vector<int>> b(n, vector<int>(m));
map<vector<int>, int> mp_col;
for (int i = 0; i < n; i++) {
vector<int> temp;
for (int j = 0; j < m; j++) {
cin >> a[i][j];
temp.emplace_back(a[i][j]);
}
sort(temp.begin(), temp.end());
mp_col[temp]++;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> b[i][j];
}
}
for (int i = 0; i < n; i++) {
vector<int> temp;
for (int j = 0; j < m; j++) {
temp.emplace_back(b[i][j]);
}
sort(temp.begin(), temp.end());
if (mp_col[temp] == 0) {
cout << "NO" << endl;
return;
}
}
map<vector<int>, int> mp_row;
for (int j = 0; j < m; j++) {
vector<int> temp;
for (int i = 0; i < n; i++) {
temp.emplace_back(a[i][j]);
}
sort(temp.begin(), temp.end());
mp_row[temp]++;
}
for (int j = 0; j < m; j++) {
vector<int> temp;
for (int i = 0; i < n; i++) {
temp.emplace_back(b[i][j]);
}
sort(temp.begin(), temp.end());
if (mp_row[temp] == 0) {
cout << "NO" << endl;
return;
}
}
cout << "YES" << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
F1.Field Division (easy version)(思维)
题意:
这是问题的简易版;它与困难版的区别仅在于问题。简易版只需输出某些值是否为非零。而困难版则需要输出确切的数值。
爱丽丝和鲍勃正在分割田地。田地是一个大小为 n × m n \times m n×m( 2 ≤ n , m ≤ 1 0 9 2\le n,m\le 10^9 2≤n,m≤109) 的矩形,行的编号从上到下为 1 1 1至 n n n,列的编号从左到右为 1 1 1至 m m m。位于行 r r r和列 c c c交点的单元格表示为( r , c r,c r,c)。
鲍勃有 k k k( 2 ≤ k ≤ 2 ⋅ 1 0 5 2\le k\le 2\cdot 10^5 2≤k≤2⋅105)个喷泉,它们都位于字段的不同单元格中。爱丽丝负责分割田块,但她必须满足几个条件:
- 要分割田块,爱丽丝要从田块左侧或上侧的任意空闲(无喷泉)格子开始移动,每次移动都要向下或向右移动到相邻的格子。她的路径将在田地的右侧或底侧结束。
- 爱丽丝的路径会将田地分成两部分–一部分属于爱丽丝(这部分包括她路径上的单元格),另一部分属于鲍勃。
- 爱丽丝将拥有包含单元格( n , 1 n,1 n,1)的部分。
- 鲍勃将拥有包括单元格( 1 , m 1,m 1,m)的部分。
爱丽丝希望以这样的方式划分区域,以获得尽可能多的单元格。
鲍勃希望保留所有喷泉的所有权,但他可以将其中一个喷泉交给爱丽丝。首先,输出整数 α \alpha α-如果鲍勃不给爱丽丝任何喷泉(即所有喷泉都保留在鲍勃的地块上),爱丽丝地块的最大可能面积。然后输出 k k k个非负整数 a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,…,ak,其中:
- a i = 0 a_i=0 ai=0,如果鲍勃给了爱丽丝 i i i个喷泉后,爱丽丝的地块的最大可能大小没有增加(即仍然等于 α \alpha α);
- a i = 1 a_i=1 ai=1,如果鲍勃给了爱丽丝 i i i个喷泉后,爱丽丝的地块的最大可能大小增加了(即大于 α \alpha α)。
分析:
手推一下可以发现这个平面面积的最大是:从左到右喷泉的最低面跟平面底部的差值的总和,然后如果某个喷泉会使得这个最低面增大,那么去掉这个喷泉就可以使最终答案增大,那么可以用 m a p map map存下二维中第 c c c列中的最大值 r r r,从左到右维护一个当前最大值,计算当前的面积总和,如果某一列使得这个最大值扩大了,那么就标记这一列,最后只需要判断这个喷泉是不是所在列的最大值以及所在列是不是被标记即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 2e5 + 10;
const int MOD = 998244353;
int ar[N], ac[N];
void solve() {
int n, m, k;
cin >> n >> m >> k;
map<int, int> mp;
map<int, bool> fc;
int minn = 0;
for (int i = 0; i < k; i++) {
cin >> ar[i] >> ac[i];
minn = max(minn, ac[i]);
mp[ac[i]] = max(ar[i], mp[ac[i]]);
}
LL res = 0;
int maxx = 0, le = 1;
for (auto [c, r]: mp) {
res += (LL) (c - le) * (n - maxx);
if (r > maxx) {
fc[c] = 1;
maxx = r;
}
le = c;
}
res += (LL) (m - le + 1) * (n - maxx);
cout << res << endl;
for (int i = 0; i < k; i++) {
if (fc[ac[i]] && ar[i] == mp[ac[i]])
cout << 1 << " ";
else
cout << 0 << " ";
}
cout << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
F2.Field Division (hard version)(数学)
题意:
这是问题的困难版本;它与简单版本的区别仅在于问题。简易版只需输出某些值是否为非零。而困难版则需要输出确切的数值。
爱丽丝和鲍勃正在分割田地。田地是一个大小为 n × m n\times m n×m( 2 ≤ n , m ≤ 1 0 9 2\le n,m\le 10^9 2≤n,m≤109)的矩形;行的编号从上到下为 1 1 1至 n n n,列的编号从左到右为 1 1 1至 m m m。位于第 r r r行和第 c c c列交叉点的单元格表示为( r , c r,c r,c)。
鲍勃有 k k k( 2 ≤ k ≤ 2 ⋅ 1 0 5 2\le k\le 2\cdot 10^5 2≤k≤2⋅105)个喷泉,它们都位于字段的不同单元格中。爱丽丝负责分割田块,但她必须满足几个条件:
- 要分割田块,爱丽丝要从田块左侧或上侧的任意空闲(无喷泉)格子开始移动,每次移动都要向下或向右移动到相邻的格子。她的路径将在田地的右侧或底侧结束。
- 爱丽丝的路径会将田地分成两部分–一部分属于爱丽丝(这部分包括她路径上的单元格),另一部分属于鲍勃。
- 爱丽丝将拥有包含单元格( n , 1 n,1 n,1)的部分。
- 鲍勃将拥有包括单元格( 1 , m 1,m 1,m)的部分。
爱丽丝希望以这样的方式划分区域,以获得尽可能多的单元格。
鲍勃希望保留所有喷泉的所有权,但他可以将其中一个喷泉交给爱丽丝。首先,输出整数 α \alpha α-如果鲍勃不给爱丽丝任何喷泉(即所有喷泉都保留在鲍勃的地块上),爱丽丝地块的最大可能面积。
然后输出 k k k个非负整数 a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,…,ak,其中 a i a_i ai是这样一个值:当鲍勃给了爱丽丝 i i i个喷泉后,她的地块的最大面积将是 α + a i \alpha+a_i α+ai。
分析:
仍然可以使用简单版的思路来预先计算所存储的前缀信息。
爱丽丝的地块面积只有在移除第三种喷泉位置时才会发生变化,我们将其称为角落。
只有当喷泉被移除时,它才会停止成为一个角落,因为前缀上最左边的角落在移除后无法再向左移动。
为了计算面积的变化,我们要注意一个重要的问题:如果喷泉 j j j不是角落,那么它要么不能成为角落,要么只有在移除排序在它之前的最后一个角落后才能成为角落。它是喷泉 j j j前面最左边的一个角落,严格来说,排序后的下一个角落会更高。
因此,我们需要做的工作如下:计算每个角落的面积,在计算下一个角落之前不考虑它,这将得益于我们在计算面积时进行的预计算。每个喷泉只需处理一次,因此时间复杂度为 O ( n ) O(n) O(n)。
代码:
#include <bits/stdc++.h>
typedef long long LL;
#define x first
#define y second
#define all(a) a.begin(), a.end()
using namespace std;
bool cmp(pair<LL, LL> &a, pair<LL, LL> &b) {
if (a.x != b.x) return a.x > b.x;
return a.y < b.y;
}
void solve(LL tc) {
LL n, m, k;
cin >> n >> m >> k;
vector<pair<LL, LL>> a(k);
map<pair<LL, LL>, LL> idx;
for (LL i = 0; i < k; ++i) {
cin >> a[i].x >> a[i].y;
idx[a[i]] = i;
}
idx[{0, 0}] = k++;
a.emplace_back(0, 0);
sort(all(a), cmp);
vector<LL> ans(k);
vector<LL> total(k + 1), cur(k + 1, m + 1), last(k + 1, n);
for (LL i = 1; i <= k; ++i) {
auto e = a[i - 1];
total[i] = total[i - 1];
cur[i] = cur[i - 1];
last[i] = last[i - 1];
if (cur[i] > e.y) {
ans[idx[e]] = 1;
total[i] += (cur[i] - 1) * (last[i] - e.x);
cur[i] = e.y;
last[i] = e.x;
}
}
cout << total[k] << "\n";
for (LL i = 1; i <= k; ++i) {
auto e = a[i - 1];
if (ans[idx[e]] == 0)continue;
LL tot = total[i - 1];
LL cr = cur[i - 1];
LL lst = last[i - 1];
for (LL j = i + 1; j <= k; ++j) {
auto ee = a[j - 1];
if (cr > ee.y) {
tot += (cr - 1) * (lst - ee.x);
cr = ee.y;
lst = ee.x;
}
if (ans[idx[ee]] == 1) {
ans[idx[e]] = tot - total[j];
break;
}
}
}
ans.pop_back();
for (LL e: ans) cout << e << " ";
}
int main() {
LL t;
cin >> t;
for (LL i = 1; i <= t; ++i) {
solve(i);
cout << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。