A.Entertainment in MAC(字符串)
题意:
恭喜你,你被硕士援助中心录取了!但是,你在课堂上感到非常无聊,于是你给自己想了一个游戏。
给你一个字符串 s s s和一个偶数整数 n n n。你可以对它进行两种运算:
- 将字符串反向并 s s s添加到字符串 s s s的末尾(例如,如果 s = c p m s=cpm s=cpm,那么在执行操作之后字符串变为 s = c p m m p c s=cpmmpc s=cpmmpc)。
- 将当前字符串 s s s倒转(例如,如果 s = c p m s=cpm s=cpm,则在执行操作后变为 s = m p c s=mpc s=mpc)。
确定在应用了恰好 n n n次操作后可以得到的字典序最小的 † ^{\dagger} †字符串。请注意,可以按照任意顺序执行不同类型的操作,但必须总共执行恰好 n n n次操作。
† ^{\dagger} †当且仅当以下条件之一成立时,字符串 a a a在词法上小于字符串 b b b:
- a a a是 b b b的前缀,但是 a ≠ b a\ne b a=b ;
- 在 a a a和 b b b不同的第一个位置上,字符串 a a a的字母在字母表中出现的时间早于 b b b中的相应字母。
分析:
我们注意到当反转字符串后如果字典序减小了,那么就不会再执行反转操作,而是将反转后的字符串拼接(这样一定会构造出一个回文串),那么之后的操作就没有任何意义了,将 n n n次的操作化简为 2 2 2次。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
string s, ans;
cin >> s;
ans = min(s, string(s.rbegin(), s.rend()) + s);
cout << ans << endl;
}
return 0;
}
B.Informatics in MAC(区间)
题意:
在硕士援助中心, N y a m − N y a m Nyam-Nyam Nyam−Nyam接受了一项信息学方面的家庭作业。
有一个长度为 n n n的数组 a a a,你想把它分成 k > 1 k \gt 1 k>1个子段 † ^{\dagger} †,使每个子段上的 MEX ‡ \operatorname{MEX}^{\ddagger} MEX‡都等于相同的整数。
请帮助 N y a m − N y a m Nyam-Nyam Nyam−Nyam找到合适的除法,或者确定不存在合适的除法。
† ^{\dagger} † 将数组划分为 k k k个子数段的定义是 k k k对整数 ( l 1 , r 1 ) , ( l 2 , r 2 ) , … , ( l k , r k ) (l_1,r_1),(l_2,r_2),\ldots,(l_k,r_k) (l1,r1),(l2,r2),…,(lk,rk),使得 l i ≤ r i l_i\le r_i li≤ri和每个 1 ≤ j ≤ k − 1 1\le j\le k-1 1≤j≤k−1, l j + 1 = r j + 1 l_{j+1}=r_j+1 lj+1=rj+1,以及 l 1 = 1 l_1=1 l1=1和 r k = n r_k=n rk=n。这些数对表示子段本身。
数组中的 ‡ MEX ^{\ddagger}\operatorname{MEX} ‡MEX是不属于该数组的最小非负整数。
例如
- 数组 [ 2 , 2 , 1 ] [2,2,1] [2,2,1]的 MEX \operatorname{MEX} MEX是 0 0 0,因为 0 0 0不属于该数组。
- 数组 [ 3 , 1 , 0 , 1 ] [3,1,0,1] [3,1,0,1]中的 MEX \operatorname{MEX} MEX是 2 2 2,因为 0 0 0和 1 1 1属于数组,而 2 2 2不属于数组。
- 数组 [ 0 , 3 , 1 , 2 ] [0,3,1,2] [0,3,1,2]中的 MEX \operatorname{MEX} MEX是 4 4 4,因为 0 0 0、 1 1 1、 2 2 2和 3 3 3属于数组,而 4 4 4不属于数组。
分析:
如果有解,一定能只划分为两个区间。如果两个区间的 MEX \operatorname{MEX} MEX相同,那么合并了也是相同的。将问题转化为求前缀和后缀那个点的 MEX \operatorname{MEX} MEX相同。遍历并维护 MEX \operatorname{MEX} MEX即可。
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1), al(n + 1), ar(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
ar[a[i]]++;
}
int l, r;
l = r = 0;
while (ar[r] > 0) {
r++;
}
for (int i = 1; i < n; i++) {
al[a[i]]++;
ar[a[i]]--;
if (ar[a[i]] == 0) {
if (r > a[i]) {
r = a[i] + 1;
}
}
while (al[l] > 0) {
l++;
}
while (r > 0 && ar[r - 1] == 0) {
r--;
}
if (l == r) {
cout << 2 << endl;
cout << 1 << ' ' << i << endl;
cout << i + 1 << ' ' << n << endl;
return;
}
}
cout << -1 << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
C.Messenger in MAC(贪心)
题意:
一个APP计划进行更新,开发人员希望在更新中优化显示给用户的信息集。目前共有 n n n条信息。每条信息由两个整数 a i a_i ai和 b i b_i bi表示。阅读数字为 p 1 , p 2 , … , p k p_1,p_2,\ldots,p_k p1,p2,…,pk( 1 ≤ p i ≤ n 1\le p_i\le n 1≤pi≤n,所有 p i p_i pi都是不同的)的信息所花费的时间由公式计算得出:
∑ i = 1 k a p i + ∑ i = 1 k − 1 ∣ b p i − b p i + 1 ∣ \Large\sum_{i=1}^{k}a_{p_i}+\sum_{i=1}^{k-1}|b_{p_i}-b_{p_{i+1}}| i=1∑kapi+i=1∑k−1∣bpi−bpi+1∣
注意,读取由一条编号为 p 1 p_1 p1的报文组成的报文集所需的时间等于 a p 1 a_{p_1} ap1。此外,读取一组空信息的时间为 0 0 0。
用户可以确定他愿意在APP上花费的时间 l l l。APP必须告知用户信息集的最大可能大小,其阅读时间不超过 l l l。请注意,信息集的最大大小可以等于 0 0 0。
开发人员未能实现这一功能,帮助他们解决这一问题。
分析:
同样的元素集合,一定是按照 b b b升序排列花费最小。所以将 b b b进行升序排序,枚举左右两个端点, b b b的总和就确定了。取区间中尽量多的最小的 a a a,使得花费小于等于 l l l。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
void solve() {
LL n, m;
cin >> n >> m;
vector<pair<LL, LL>> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i].first >> a[i].second;
}
sort(a.begin(), a.end(), [&](auto a, auto b) { return a.second < b.second; });
LL ans = 0;
for (int i = 1; i <= n; i++) {
LL cnt = -a[i].second;
priority_queue<int> q;
for (int j = i; j <= n; j++) {
q.push(a[j].first);
cnt += a[j].first;
if (!q.empty() && cnt + a[j].second > m) {
cnt -= q.top();
q.pop();
}
ans = max(ans, (LL) q.size());
}
}
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D.Exam in MAC(容斥)
题意:
硕士生中心公布了入学考试,考试内容如下。
给考生一个大小为 n n n的集合 s s s和一个整数 c c c。对于这个集合,计算出有多少对整数 ( x , y ) (x,y) (x,y)使得 0 ≤ x ≤ y ≤ c 0\leq x\leq y\leq c 0≤x≤y≤c,满足 x + y x+y x+y不包含在集合 s s s中,以及 y − x y-x y−x不包含在集合 s s s中。
分析:
本题考虑容斥原理:
- 总数 − ( x + y ) -(x+y) −(x+y)属于 a [ i ] a[i] a[i]的方案数 − ( x − y ) -(x-y) −(x−y)属于 a [ i ] a[i] a[i]的方案数 + ( x + y ) +(x+y) +(x+y)且 ( x − y ) (x-y) (x−y)属于 a [ i ] a[i] a[i]的方案数;
- 对于 x + y = a [ i ] x+y=a[i] x+y=a[i]的个数就是 a [ i ] / 2 + 1 a[i]/2+1 a[i]/2+1;
- 对于 y − x = a [ i ] y-x=a[i] y−x=a[i]的个数就是 c − a [ i ] + 1 c-a[i]+1 c−a[i]+1的个数
- 对于重复的,发现如果 x + y x+y x+y和 x − y x-y x−y确定,则 x , y x,y x,y就确定了,而有解条件是 x + y x+y x+y和 x − y x-y x−y同奇偶,那么把 s s s中的元素的奇数和偶数分别统计一下即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
LL n, c;
cin >> n >> c;
LL t1, t2, t3, t4;
t1 = t2 = t3 = t4 = 0;
for (LL i = 1; i <= n; i++) {
int x;
cin >> x;
t1 += (x / 2) + 1;
t2 += (c - x + 1);
if (x & 1)
t3++;
else
t4++;
}
c++;
cout << c * (c - 1) / 2 + c - t1 - t2 + t3 * (t3 - 1) / 2 + t4 * (t4 - 1) / 2 + t3 + t4 << endl;
}
return 0;
}
E.Distance Learning Courses in MAC(前缀和)
题意:
研究生中心的新年已经到来,是时候推出一项新功能了!
现在,学生可以选修远程学习课程,共有 n n n门课程可供选择。对于第 i i i门远程学习课程,学生可以获得从 x i x_i xi到 y i y_i yi不等的成绩。
但是,并不是所有的课程每个学生都可以选修。具体来说,第 j j j名学生只能选修 l j l_j lj至 r j r_j rj的课程,即 l j , l j + 1 , … , r j l_j,l_j+1,\ldots,r_j lj,lj+1,…,rj的远程学习课程。
远程学习课程的创建者决定用一种特殊的方式来确定最终成绩。让第 j j j名学生在他们的远程学习课程中获得 c l j , c l j + 1 , … , c r j c_{l_j},c_{l_j+1},\ldots,c_{r_j} clj,clj+1,…,crj 的成绩。那么他们的最终成绩将等于 c l j c_{l_j} clj ∣ | ∣ c l j + 1 c_{l_j+1} clj+1 ∣ | ∣ … \ldots … ∣ | ∣ c r j c_{r_j} crj, ∣ | ∣表示按位或。
由于解决远程学习课程的聊天机器人坏了,对于 q q q个学生中的每一个,请你告诉他们可以达到的最高期末成绩。
分析:
首先考虑 x = 0 x=0 x=0。我们从最高有效位遍历到最低有效位,并尝试将它们包含在答案中。假设我们遍历第 i i i位,那么如果这样的一位在 y y y个数字中出现了 c c c次,就会出现几种情况:
- c = 0 c=0 c=0 - 答案中不能包含该位
- c = 1 c=1 c=1 - 该位将包含在答案中,我们将其加上
- c > 1 c\gt 1 c>1 - 一种特殊情况,因为对于一个有位x的数字,我们可以设置它,而对于另一个数字,我们可以设置所有位 j < i j\lt i j<i 。
因此,如果我们遇到某个位 i i i出现不止一次,那么答案中也会包含所有位 j ≤ i j\le i j≤i。
然后考虑原问题,我们取一对数字 ( x i , y i ) (x_i,y_i) (xi,yi),并找到按位最大的公共前缀,称之为数 w w w。显然, w w w的所有位都将包含在答案中,然后我们再建立一对新的数对 ( x i ′ , y i ′ ) (x^{'}_{i},y^{'}_{i}) (xi′,yi′)= ( x i − w , y i − w ) (x_i-w,y_i-w) (xi−w,yi−w),并记住数字 w i w_i wi。注意数字 y i ′ − 1 ≥ x i ′ y^{'}_{i}-1\ge x^{'}_{i} yi′−1≥xi′。在某些位出现不止一次的情况下,我们会把它和所有更小的位加到答案中。为此,我们将在这个位置上设置一个等于 2 i − 1 2^i-1 2i−1的数字(而其他较大的比特位则设置为 i i i)。但如果是 y i − 1 ′ ≥ x i ′ y^{'}_{i-1}\ge x^{'}_{i} yi−1′≥xi′ ,那么我们总是可以将所有这些位相加。
因此,求 j j j的求解算法如下:
- 取所有 w i w_i wi的按位或结果( l j ≤ i ≤ r j l_j\le i\le r_j lj≤i≤rj),设为数字 W W W。
- 对位 i i i进行遍历,与 x = 0 x=0 x=0的情况类似,对数组 y ′ y^{'} y′进行遍历。同时,考虑到该位出现在 W W W中。
这种解法可以使用每个位的前缀和来实现。时间复杂度为 O ( n ⋅ log n ) O(n\cdot\log n) O(n⋅logn)。
代码:
#include <bits/stdc++.h>
using namespace std;
template<class T>
using vc = vector<T>;
const int N = 2e5;
const int bit = 30;
struct segtree {
vc<int> t;
int n;
segtree(int n) : n(n) {
t.resize(n * 2);
}
void upd(int i, int x) {
for (t[i += n] = x; i > 1; i >>= 1) {
t[i >> 1] = t[i] | t[i ^ 1];
}
}
int get(int l, int r) {
int res = 0;
for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
if (l & 1)
res |= t[l++];
if (r & 1)
res |= t[--r];
}
return res;
}
};
int n;
int l[N], r[N];
int w[N];
void fix() {
for (int i = 0; i < n; ++i) {
if (l[i] == r[i]) {
w[i] = l[i];
l[i] = r[i] = 0;
continue;
}
int pref = (1 << (__lg(l[i] ^ r[i]) + 1)) - 1;
w[i] = r[i] - (r[i] & pref);
r[i] &= pref;
l[i] &= pref;
}
}
void solve() {
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> l[i] >> r[i];
}
fix();
segtree t(n);
vc<vc<int>> bits(bit, vc<int>(n + 1));
for (int i = 0; i < n; ++i) {
t.upd(i, w[i]);
for (int j = 0; j < bit; ++j) {
bits[j][i + 1] = bits[j][i];
if (r[i] >> j & 1) {
bits[j][i + 1]++;
}
}
}
int q;
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
--x;
int ans = t.get(x, y);
for (int j = bit - 1; j >= 0; --j) {
int cnt = bits[j][y] - bits[j][x] + (ans >> j & 1);
if (cnt > 1) {
ans |= (2 << j) - 1;
break;
} else if (cnt == 1) {
ans |= 1 << j;
}
}
cout << ans << ' ';
}
cout << "\n";
}
int main() {
ios::sync_with_stdio(false);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。