A.Entertainment in MAC(字符串)
题意:
恭喜你,你被硕士援助中心录取了!但是,你在课堂上感到非常无聊,于是你给自己想了一个游戏。
给你一个字符串sss和一个偶数整数nnn。你可以对它进行两种运算:
- 将字符串反向并sss添加到字符串sss的末尾(例如,如果s=cpms=cpms=cpm,那么在执行操作之后字符串变为s=cpmmpcs=cpmmpcs=cpmmpc)。
- 将当前字符串sss倒转(例如,如果s=cpms=cpms=cpm,则在执行操作后变为s=mpcs=mpcs=mpc)。
确定在应用了恰好nnn次操作后可以得到的字典序最小的†^{\dagger}†字符串。请注意,可以按照任意顺序执行不同类型的操作,但必须总共执行恰好nnn次操作。
†^{\dagger}†当且仅当以下条件之一成立时,字符串aaa在词法上小于字符串bbb:
- aaa是bbb的前缀,但是a≠ba\ne ba=b ;
- 在aaa和bbb不同的第一个位置上,字符串aaa的字母在字母表中出现的时间早于bbb中的相应字母。
分析:
我们注意到当反转字符串后如果字典序减小了,那么就不会再执行反转操作,而是将反转后的字符串拼接(这样一定会构造出一个回文串),那么之后的操作就没有任何意义了,将nnn次的操作化简为222次。
代码:
#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(区间)
题意:
在硕士援助中心,Nyam−NyamNyam-NyamNyam−Nyam接受了一项信息学方面的家庭作业。
有一个长度为nnn的数组aaa,你想把它分成k>1k \gt 1k>1个子段†^{\dagger}†,使每个子段上的MEX‡\operatorname{MEX}^{\ddagger}MEX‡都等于相同的整数。
请帮助Nyam−NyamNyam-NyamNyam−Nyam找到合适的除法,或者确定不存在合适的除法。
†^{\dagger}† 将数组划分为kkk个子数段的定义是kkk对整数(l1,r1),(l2,r2),…,(lk,rk)(l_1,r_1),(l_2,r_2),\ldots,(l_k,r_k)(l1,r1),(l2,r2),…,(lk,rk),使得li≤ril_i\le r_ili≤ri和每个1≤j≤k−11\le j\le k-11≤j≤k−1,lj+1=rj+1l_{j+1}=r_j+1lj+1=rj+1,以及l1=1l_1=1l1=1和rk=nr_k=nrk=n。这些数对表示子段本身。
数组中的‡MEX^{\ddagger}\operatorname{MEX}‡MEX是不属于该数组的最小非负整数。
例如
- 数组[2,2,1][2,2,1][2,2,1]的MEX\operatorname{MEX}MEX是000,因为000不属于该数组。
- 数组[3,1,0,1][3,1,0,1][3,1,0,1]中的MEX\operatorname{MEX}MEX是222,因为000和111属于数组,而222不属于数组。
- 数组[0,3,1,2][0,3,1,2][0,3,1,2]中的MEX\operatorname{MEX}MEX是444,因为000、111、222和333属于数组,而444不属于数组。
分析:
如果有解,一定能只划分为两个区间。如果两个区间的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计划进行更新,开发人员希望在更新中优化显示给用户的信息集。目前共有nnn条信息。每条信息由两个整数aia_iai和bib_ibi表示。阅读数字为p1,p2,…,pkp_1,p_2,\ldots,p_kp1,p2,…,pk(1≤pi≤n1\le p_i\le n1≤pi≤n,所有pip_ipi都是不同的)的信息所花费的时间由公式计算得出:
∑i=1kapi+∑i=1k−1∣bpi−bpi+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∣
注意,读取由一条编号为p1p_1p1的报文组成的报文集所需的时间等于ap1a_{p_1}ap1。此外,读取一组空信息的时间为000。
用户可以确定他愿意在APP上花费的时间lll。APP必须告知用户信息集的最大可能大小,其阅读时间不超过lll。请注意,信息集的最大大小可以等于000。
开发人员未能实现这一功能,帮助他们解决这一问题。
分析:
同样的元素集合,一定是按照bbb升序排列花费最小。所以将bbb进行升序排序,枚举左右两个端点,bbb的总和就确定了。取区间中尽量多的最小的aaa,使得花费小于等于lll。
代码:
#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(容斥)
题意:
硕士生中心公布了入学考试,考试内容如下。
给考生一个大小为nnn的集合sss和一个整数ccc。对于这个集合,计算出有多少对整数(x,y)(x,y)(x,y)使得0≤x≤y≤c0\leq x\leq y\leq c0≤x≤y≤c,满足x+yx+yx+y不包含在集合sss中,以及y−xy-xy−x不包含在集合sss中。
分析:
本题考虑容斥原理:
- 总数−(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+1a[i]/2+1a[i]/2+1;
- 对于y−x=a[i]y-x=a[i]y−x=a[i]的个数就是c−a[i]+1c-a[i]+1c−a[i]+1的个数
- 对于重复的,发现如果x+yx+yx+y和x−yx-yx−y确定,则x,yx,yx,y就确定了,而有解条件是x+yx+yx+y和x−yx-yx−y同奇偶,那么把sss中的元素的奇数和偶数分别统计一下即可。
代码:
#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(前缀和)
题意:
研究生中心的新年已经到来,是时候推出一项新功能了!
现在,学生可以选修远程学习课程,共有nnn门课程可供选择。对于第iii门远程学习课程,学生可以获得从xix_ixi到yiy_iyi不等的成绩。
但是,并不是所有的课程每个学生都可以选修。具体来说,第jjj名学生只能选修ljl_jlj至rjr_jrj的课程,即lj,lj+1,…,rjl_j,l_j+1,\ldots,r_jlj,lj+1,…,rj的远程学习课程。
远程学习课程的创建者决定用一种特殊的方式来确定最终成绩。让第jjj名学生在他们的远程学习课程中获得clj,clj+1,…,crjc_{l_j},c_{l_j+1},\ldots,c_{r_j}clj,clj+1,…,crj 的成绩。那么他们的最终成绩将等于cljc_{l_j}clj∣|∣clj+1c_{l_j+1}clj+1∣|∣…\ldots…∣|∣crjc_{r_j}crj,∣|∣表示按位或。
由于解决远程学习课程的聊天机器人坏了,对于qqq个学生中的每一个,请你告诉他们可以达到的最高期末成绩。
分析:
首先考虑x=0x=0x=0。我们从最高有效位遍历到最低有效位,并尝试将它们包含在答案中。假设我们遍历第iii位,那么如果这样的一位在yyy个数字中出现了ccc次,就会出现几种情况:
- c=0c=0c=0 - 答案中不能包含该位
- c=1c=1c=1 - 该位将包含在答案中,我们将其加上
- c>1c\gt 1c>1 - 一种特殊情况,因为对于一个有位x的数字,我们可以设置它,而对于另一个数字,我们可以设置所有位j<ij\lt ij<i 。
因此,如果我们遇到某个位iii出现不止一次,那么答案中也会包含所有位j≤ij\le ij≤i。
然后考虑原问题,我们取一对数字(xi,yi)(x_i,y_i)(xi,yi),并找到按位最大的公共前缀,称之为数www。显然,www的所有位都将包含在答案中,然后我们再建立一对新的数对(xi′,yi′)(x^{'}_{i},y^{'}_{i})(xi′,yi′)=(xi−w,yi−w)(x_i-w,y_i-w)(xi−w,yi−w),并记住数字wiw_iwi。注意数字yi′−1≥xi′y^{'}_{i}-1\ge x^{'}_{i}yi′−1≥xi′。在某些位出现不止一次的情况下,我们会把它和所有更小的位加到答案中。为此,我们将在这个位置上设置一个等于2i−12^i-12i−1的数字(而其他较大的比特位则设置为iii)。但如果是yi−1′≥xi′y^{'}_{i-1}\ge x^{'}_{i}yi−1′≥xi′ ,那么我们总是可以将所有这些位相加。
因此,求jjj的求解算法如下:
- 取所有wiw_iwi的按位或结果(lj≤i≤rjl_j\le i\le r_jlj≤i≤rj),设为数字WWW。
- 对位iii进行遍历,与x=0x=0x=0的情况类似,对数组y′y^{'}y′进行遍历。同时,考虑到该位出现在 WWW中。
这种解法可以使用每个位的前缀和来实现。时间复杂度为O(n⋅logn)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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

657

被折叠的 条评论
为什么被折叠?



