A.X Axis(暴力)
题意:
在XXX轴(1≤xi≤101\leq x_i\leq 101≤xi≤10)上有三个点,其整数坐标分别为x1x_1x1、x2x_2x2和x3x_3x3。您可以选择XXX轴上任何一个整数坐标为aaa的点。请注意,点aaa可能与x1x_1x1、x2x_2x2或x3x_3x3重合。设f(a)f(a)f(a)是给定点到点aaa的总距离。求f(a)f(a)f(a)的最小值。
点aaa与bbb之间的距离等于∣a−b∣|a-b|∣a−b∣。例如,点a=5a=5a=5与b=2b=2b=2之间的距离为333。
分析:
暴力枚举一下所有可能,取最小值即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
void solve() {
LL a, b, c;
cin >> a >> b >> c;
LL ans = 100;
for (LL i = 0; i <= 10; i++) {
ans = min(ans, abs(i - a) + abs(i - b) + abs(i - c));
}
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
B.Matrix Stabilization(模拟)
题意:
给你一个大小为n×mn\times mn×m的矩阵,其中行的编号从上到下为111至nnn,列的编号从左到右为111至mmm。位于第iii行和第jjj列交点上的元素用aija_{ij}aij表示。
考虑稳定矩阵aaa的算法:
- 找到单元格(i,j)(i,j)(i,j)使其值严格大于所有相邻单元格的值。如果没有这样的单元格,则终止算法。如果有多个这样的单元格,则选择iii值最小的单元格;如果仍有多个单元格,则选择jjj值最小的单元格。
- 设置 aij=aij−1a_{ij}=a_{ij}-1aij=aij−1。
- 转到步骤111。
在此问题中,如果单元格(a,b)(a,b)(a,b)和(c,d)(c,d)(c,d)有一个共同的边,即∣a−c∣+∣b−d∣=1|a-c|+|b-d|=1∣a−c∣+∣b−d∣=1,那么这两个单元格就被视为相邻单元格。
您的任务是在执行稳定算法后输出矩阵aaa。可以证明这种算法不可能运行无限次迭代。
分析:
按题意进行模拟。从上到下,从左到右枚举元素,每次如果此元素严格大于周围的元素,就赋值为周围四个元素中最大的。因为操作以后还是没有严格小于周围的元素,所以操作后不会使得前面的元素不符合要求。
注意,边上的元素取最大值时可能会超出边界,所以每组数据要把边界外的元素都设为000。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 2005;
LL a[N][N];
void solve() {
LL n, m;
cin >> n >> m;
for (LL i = 0; i <= n + 1; i++) {
a[i][0] = 0;
a[i][m + 1] = 0;
}
for (LL i = 0; i <= m + 1; i++) {
a[0][i] = 0;
a[n + 1][i] = 0;
}
for (LL i = 1; i <= n; i++) {
for (LL j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
for (LL i = 1; i <= n; i++) {
for (LL j = 1; j <= m; j++) {
a[i][j] = min(a[i][j], max(a[i - 1][j], max(a[i + 1][j], max(a[i][j - 1], a[i][j + 1]))));
cout << a[i][j] << ' ';
}
cout << endl;
}
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
C.Update Queries(排序)
题意:
下面是一个简单的问题。给你一个长度为nnn的字符串sss(由小写拉丁字母组成),以及一个长度为mmm(1≤indi≤n1\leq ind_i\leq n1≤indi≤n)的索引数组indindind和一个长度为mmm的字符串ccc(由小写拉丁字母组成)。然后,按顺序执行更新操作,即在第iii个操作中,设置sindi=cis_{ind_i}=c_isindi=ci。请注意,所有的mmm操作都是从第一个操作到最后一个操作。
当然,如果改变数组indindind中索引的顺序和/或字符串ccc中字母的顺序,会得到不同的结果。如果数组indindind中的索引和字符串ccc中的字母顺序可以随意改变,那么在进行mmm次更新操作后,可以得到的字典序最小的字符串sss。
当且仅当满足以下条件之一时,字符串aaa在字典序上小于字符串bbb:
- aaa是bbb的前缀,但a≠ba\neq ba=b;
- 在aaa和bbb不同的第一个位置,字符串aaa中的符号在字母表中的位置比字符串bbb中的相应符号早。
分析:
我们发现idnidnidn和ccc都可以重新排列,考虑对其进行排序。如果idnidnidn的元素全部满足两两不相同,则直接在排序后按顺序填入即可,因为这样保证字典序小的排在了前面。对于相同的元素,先插入的元素会被后面的覆盖掉,只需要考虑最后一次操作,而前面的操作则用字典序最大的来顶替即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 1e5 + 5;
int idn[N];
string s, t;
void solve() {
LL n, m;
t = "";
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
cin >> idn[i];
for (int i = 1; i <= m; i++) {
char tmp;
cin >> tmp;
t += tmp;
}
sort(idn + 1, idn + 1 + m);
sort(t.begin(), t.end());
deque<char> q;
for (int i = 0; i < t.size(); i++)
q.push_back(t[i]);
for (int i = 1; i <= m; i++) {
if (idn[i] != idn[i - 1]) {
s[idn[i] - 1] = q.front();
q.pop_front();
} else {
q.pop_back();
}
}
cout << s << endl;
}
int main() {
int T;
cin >> T;
while (T--)
solve();
return 0;
}
D.Mathematical Problem(数学)
题意:
给你一个长度n>1n\gt 1n>1的字符串sss,由从000到999的数字组成。您必须在这个字符串中准确插入n−2n-2n−2个+++(加法)符号或×\times×(乘法)符号,才能形成一个有效的算术表达式。
在本题中,符号不能放在字符串sss的第一个字符之前或最后一个字符之后,也不能连续写入两个符号。此外,请注意字符串中数字的顺序不能改变。以s=987009s=987009s=987009为例:
- 从这个字符串可以得到以下算术表达式:9×8+70×0+9=819\times 8+70\times 0+9=819×8+70×0+9=81,98×7×0+0×9=098\times 7\times 0+0\times 9=098×7×0+0×9=0,9+8+7+0+09=9+8+7+0+9=339+8+7+0+09=9+8+7+0+9=339+8+7+0+09=9+8+7+0+9=33。请注意,数字090909被认为是有效的,并被转换为999。
- 从该字符串中无法得到以下算术表达式:+9×8×70+09+9\times 8\times 70+09+9×8×70+09(符号只能放在数字之间)、98×70+0+998\times 70+0+998×70+0+9(因为有666个数字,所以必须正好有444个符号)。
算术表达式的结果是根据数学规则计算出来的–首先进行所有乘法运算,然后是加法运算。您需要找出在给定的字符串sss中插入恰好n−2n-2n−2个加法或乘法符号所能得到的最小结果。
分析:
本题我们分情况考虑
考虑n=2n=2n=2的情况,不能加运算符,如果第一个字符为000,则输出第二个字符,否则输出sss。
考虑n=3n=3n=3且有000,000在中间的情况,答案为第一个和第三个字符乘积或者相加中较小的一个。
其他长度的情况:如果有000,则将所有运算符置为乘,最后结果就是000。如果没有000,则将sss中所有不为111的数统计求和。此时的求和有n−1n-1n−1个运算符,其中涉及到111的运算符全为*,不涉及111的运算符全为+。此时需要去掉一个运算符,再次遍历sss,依此考虑将sss中任意两个相邻的字符组合成一个二位数字,看哪个组合最后得到的结果最小,取最小值即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL MAXN = 0x3f3f3f3f;
string s;
void solve() {
int n;
cin >> n >> s;
if (n == 2) {
if (s[0] == '0')
cout << s[1] << endl;
else
cout << s << endl;
} else if (n == 3 && s[1] == '0') {
cout << min(s[0] + s[2] - '0' * 2, (s[0] - '0') * (s[2] - '0')) << endl;
} else if (s.find('0') != std::string::npos) {
cout << "0" << endl;
} else {
int sum = 0;
for (const auto &x: s) {
if (x > '1') {
sum += x - '0';
}
}
int ans = MAXN;
for (int i = 0; i < n - 1; ++i) {
int num = stoi(s.substr(i, 2));
ans = min(ans, sum - (s[i] == '1' ? 0 : s[i] - '0') - (s[i + 1] == '1' ? 0 : s[i + 1] - '0') + num);
}
cout << ans << endl;
}
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
E.Beautiful Array(数据结构)
题意:
给你一个整数数组a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,…,an和一个整数kkk。您需要用最少的运算使数组变得美丽。
在进行操作前,可以随意对数组元素进行洗牌。对于一次操作,可以进行以下操作:
- 选择一个索引1≤i≤n1\leq i\leq n1≤i≤n.
- 生成ai=ai+ka_i=a_i+kai=ai+k。
如果所有的1≤i≤n1\leq i\leq n1≤i≤n都是bi=bn−i+1b_i=b_{n-i+1}bi=bn−i+1,那么数组b1,b2,…,bnb_1,b_2,\ldots,b_nb1,b2,…,bn是美丽的。
求使数组美丽所需的最小运算次数,或者输出不可能。
分析:
首先利用map将aaa中的出现偶数次的元素去掉。我们发现如果一个数能通过另一个数相加若干个kkk得到,则这两个数%k的余数是相等的。将剩下的元素%k\%k%k存到map<int,vector>中,遍历这个新的map,对vector排序。如果vector中的元素数量是偶数,那么可以将相邻的两个vector作为一组进行+k+k+k变换。如果vector中的元素数量是奇数,那么这样的vector不能超过111个,维护一个前缀和后缀和来讨论vector中哪个元素作为回文中心,不参与到这种配对的计算中。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
void solve() {
int n, k;
cin >> n >> k;
map<int, int> mapp;
for (int i = 0; i < n; i++) {
int t;
cin >> t;
mapp[t]++;
}
vector<int> a;
for (const auto &[x, y]: mapp) {
if (y & 1) {
a.push_back(x);
}
}
if ((int) a.size() <= 1) {
cout << "0" << endl;
return;
}
map<int, vector<int>> rec;
for (int i = 0; i < a.size(); ++i) {
int t = a[i];
rec[t % k].push_back(t);
}
int cnt = 0;
LL ans = 0;
for (auto &[x, cur]: rec) {
bool ok = true;
if (cur.size() & 1) {
cnt++;
ok = false;
if (cnt > 1) {
cout << "-1" << endl;
return;
}
}
sort(cur.begin(), cur.end());
if (ok) {
for (int i = 1; i < cur.size(); i += 2) {
ans += (cur[i] - cur[i - 1]) / k;
}
} else if (cur.size() > 1) {
int m = (int) cur.size();
vector<LL> pref(m);
vector<LL> suff(m);
pref[1] = cur[1] - cur[0];
for (int i = 3; i < m; i += 2) {
pref[i] = pref[i - 2] + cur[i] - cur[i - 1];
}
suff[m - 2] = cur[m - 1] - cur[m - 2];
for (int i = m - 4; i >= 0; i -= 2) {
suff[i] = suff[i + 2] + cur[i + 1] - cur[i];
}
LL minn = min(pref[m - 2], suff[1]);
for (int i = 2; i < m - 2; i += 2) {
minn = min(minn, pref[i - 1] + suff[i + 1]);
}
ans += minn / k;
}
}
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}
F.Non-academic Problem(联通分量)
题意:
给你一个连通的无向图,其顶点用111到nnn之间的整数编号。你的任务是尽量减少图中存在路径的顶点1≤u<v≤n1\leq u\lt v\leq n1≤u<v≤n对的数量。为了实现这一目标,你可以从图形中删除一条边。
请找出顶点对的最小数目!
分析:
整个图是连通的,删除边双内的边并不会影响连通性,删除桥边会变成两个连通块,一个大小为xxx另一个则为n−xn−xn−x。
对于一个大小为xxx的连通块,编号最小的点有另外x−1x−1x−1个点与其对应,以此类推,因此满足条件的点对数量是:
∑i=1x−1i=x(x−1)2
\sum_{i=1}^{x-1}i=\frac{x(x-1)}{2}
i=1∑x−1i=2x(x−1)
考虑将图缩点为边双连通分量,然后只枚举桥边(即树边),对于所有情况取一个最小值即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 100010;
const int M = 200010;
int h[N], sz[N], dfn[N], low[N], id[N], szedcc[N], idx, n, m, timestamp, cntedcc;
stack<int> stk;
vector<int> G[N];
set<LL> S;
LL ans;
struct Edge {
int to, nxt;
} e[M];
void add(int a, int b) {
e[idx] = {b, h[a]};
h[a] = idx++;
}
void tarjan(int u, int from) {
stk.push(u);
dfn[u] = low[u] = ++timestamp;
for (int i = h[u]; i; i = e[i].nxt) {
int to = e[i].to;
if (!dfn[to]) {
tarjan(to, i);
low[u] = min(low[u], low[to]);
} else if (i != (from ^ 1))
low[u] = min(low[u], dfn[to]);
}
if (low[u] == dfn[u]) {
++cntedcc;
int y;
do {
y = stk.top();
stk.pop();
id[y] = cntedcc;
szedcc[cntedcc]++;
} while (y != u);
}
}
LL calc(LL x) {
return x * (x - 1) / 2;
}
void dfs(int u, int f) {
sz[u] = szedcc[u];
for (int to: G[u]) {
if (to == f)
continue;
dfs(to, u);
sz[u] += sz[to];
ans = min(ans, calc(sz[to]) + calc(n - sz[to]));
}
}
int main() {
int t;
cin >> t;
while (t--) {
cin >> n >> m;
idx = 2;
timestamp = cntedcc = 0;
S.clear();
for (int i = 0; i <= n; i++) {
h[i] = dfn[i] = szedcc[i] = sz[i] = id[i] = low[i] = 0;
vector<int>().swap(G[i]);
}
for (int i = 1, a, b; i <= m; i++) {
cin >> a >> b;
add(a, b);
add(b, a);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i, -1);
}
for (int u = 1; u <= n; u++) {
for (int i = h[u]; i; i = e[i].nxt) {
int to = e[i].to;
int A = id[u], B = id[to];
if (A < B)
swap(A, B);
if (id[u] != id[to]) {
LL hash = 1ll * A * N + B;
if (S.count(hash))
continue;
S.insert(hash);
G[id[u]].emplace_back(id[to]);
G[id[to]].emplace_back(id[u]);
}
}
}
ans = calc(n);
dfs(1, 0);
cout << ans << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

5569



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



