A.Past ABCs(判断)
题意
给出一个包含 6 6 6个字符的字符串,保证前面三个字符为 A B C ABC ABC,后面三个字符均为数字。
问,这个字符串代表的比赛是否存在?
分析
将后面三个字符转化为数字,然后判断这一场是否在范围内(即 i d ≤ 349 id \le 349 id≤349 a n d and and i d ≥ 1 id \ge 1 id≥1 a n d and and i d ≠ 316 id \ne 316 id=316)即可。
代码
#include<bits/stdc++.h>
using namespace std;
void solve() {
string s;
cin >> s;
int num = 0;
for (int i = 3; i <= 5; i++) {
num = num * 10 + s[i] - '0';
}
if (num >= 350 || num <= 1 || num == 316) {
cout << "No" << endl;
} else {
cout << "Yes" << endl;
}
}
int main() {
solve();
return 0;
}
B.Dentist Aoki(模拟)
题意
高桥有 N N N颗牙齿,牙医将对这些牙齿进行以下操作 Q Q Q次:
-
对于第 i i i次操作,选择第 T i T_i Ti颗牙齿
-
如果第 T i T_i Ti颗牙齿存在,那么拔掉它
-
如果第 T i T_i Ti颗牙齿不存在,那么种一颗牙齿上去
问,结束操作之后,高桥还剩多少颗牙齿?
分析
使用数组记录每颗牙齿的状态,如果是偶数表示存在,将牙齿总数减少1,如果是奇数表示不存在,将牙齿的数量增加一。
代码
#include<bits/stdc++.h>
using namespace std;
int P[1005];
void solve() {
int n, k;
cin >> n >> k;
for (int i = 1; i <= k; i++) {
int a;
cin >> a;
P[a]++;
if (P[a] & 1) n--;
else n++;
}
cout << n << endl;
}
int main() {
solve();
return 0;
}
C.Sort(思维)
题意
给出一个包含 N N N个数字的数组 A A A,保证 A A A是数字 1 ∼ N 1 \sim N 1∼N的一个排列,你可以执行若干次以下操作:
- 选择两个不同的下标 i , j i, j i,j,交换 i i i下标和 j j j下标上的数字。
问,最少执行多少次操作,才能使得数组 A A A变为升序排序,请你输出最小的操作次数以及每次交换位置的两个下标。
分析
将数组 A A A中数字放置与下标 1 ∼ N 1 \sim N 1∼N上,那么只要出现 A [ i ] ≠ i A[i] \ne i A[i]=i,必然就需要交换,且必然是跟数字 i i i所在的下标进行交换。
如果直接使用遍历的方式去后面进行查找,那么就会因为时间复杂度过高而导致超时。
那么该怎么办呢?
可以使用数组记录每个数字的位置,直接通过这个数组找到该交换的数字所在的下标,并模拟交换即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5e2;
int n, a[N], pos[N];
vector<pair<int, int> > ans;
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pos[a[i]] = i;
}
for (int i = 1; i <= n; i++) {
if (a[i] != i) {
int p = pos[i];
pos[a[i]] = p;
swap(a[i], a[p]);
ans.push_back({i, p});
}
}
cout << ans.size() << endl;
for (auto i : ans) {
cout << i.first << ' ' << i.second << endl;
}
}
int main() {
solve();
return 0;
}
D.New Friends(DFS)
题意
给出 N N N个人,和 M M M对关系 A i , B i A_i, B_i Ai,Bi,每对关系表示 A i A_i Ai和 B i B_i Bi互为朋友,问:你最多可以执行多少次以下操作:
- 选择三个人 X , Y , Z X, Y, Z X,Y,Z,且 X X X和 Y Y Y是朋友, Y Y Y和 Z Z Z是朋友,但 X X X和 Z Z Z不是朋友,让 X X X和 Z Z Z成为朋友
分析
不难发现,实际上本题就是给出了若干个无向连通图,问这些连通子图均成为完全图还需要多少条边。
使用dfs
记录每个连通子图中包含的点的数量
c
n
t
cnt
cnt,并计算该子图若为完全图应该包含的边数(
c
n
t
×
(
c
n
t
−
1
)
2
\frac{cnt \times (cnt - 1)}{2}
2cnt×(cnt−1))。
统计完所有子图成为完全图的边数后,减去已有的边数 M M M极为最后的答案。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5e2;
vector<int> G[N];
int n, m,vis[N];
ll ans, cnt;
void dfs(int x) {
cnt++;
for (auto i : G[x]) {
if (vis[i]) continue;
vis[i] = 1;
dfs(i);
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
if (vis[i] == 0) {
cnt = 0;
vis[i] = 1;
dfs(i);
ans += (cnt - 1) * cnt / 2;
}
}
cout << ans - m << endl;
}
int main() {
solve();
return 0;
}
E.Toward 0(记忆化搜索)
题意
给出一个数字 N N N,你可以进行若干次以下操作:
-
支付 X X X元,将 N N N变为 ⌊ N A ⌋ \lfloor \frac{N}{A} \rfloor ⌊AN⌋
-
支付 Y Y Y元,等概率的从 1 ∼ 6 1 \sim 6 1∼6选择一个整数 b b b,并将 N N N变为 ⌊ N b ⌋ \lfloor \frac{N}{b} \rfloor ⌊bN⌋
问:最优策略下,所需花费的期望是多少?
分析
由于每次操作均有两种选择, 那么对于两种选择必然会选期望花费较小的一个。
可以使用 d f s ( n ) dfs(n) dfs(n)搜索数字 n n n的最低期望花费,那么每轮有以下两种选择:
-
选择操作1,期望花费为 d f s ( n / a ) + X dfs(n / a) + X dfs(n/a)+X
-
选择操作2,期望花费为 ∑ i = 2 6 d f s ( n / i ) + Y 5 + Y \frac{\sum\limits_{i = 2}^{6}dfs(n / i) + Y}{5} + Y 5i=2∑6dfs(n/i)+Y+Y(主要不要选择b为1进入搜索,会导致进入死循环)
Tips: 操作2相当于取5次有效操作的期望( ∑ i = 2 6 d f s ( n / i ) + Y 5 \frac{\sum\limits_{i = 2}^{6}dfs(n / i) + Y}{5} 5i=2∑6dfs(n/i)+Y),加上一次无效操作的额外花费 ( Y ) (Y) (Y)
每轮选择期望花费较小的操作,并进行记忆化即可。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
map<ll, db> dp;
ll n, a, x, y;
db dfs(ll num) {
if (num <= 0) return 0;
if (dp.find(num) != dp.end()) return dp[num];
db cost_x = dfs(num / a) + x;
db cost_y = 0;
for (int i = 2; i <= 6; i++) {//注意:1操作相当于不操作
cost_y += dfs(num / i);
}
cost_y = (cost_y + y) / 5 + y;
return dp[num] = min(cost_x, cost_y);
}
void solve() {
cin >> n >> a >> x >> y;
cout << fixed << setprecision(15) << dfs(n) << endl;
}
int main() {
solve();
return 0;
}
F.Transpose(模拟)
题意
给出一个字符 S S S,你需要将所有匹配的括号中的字符串进行翻转,并将字母大小写互换,然后去掉括号,问操作结束后的字符串是什么。
分析
预处理匹配的两个括号对应的另一个括号所在的下标,然后使用搜索进行模拟。
如果遇到括号,根据记录的下标信息将括号中的内容递归处理,并跳过这段区间。
如果遇到字母,根据当前翻转次数决定当前字母是否需要进行大小写转换。
递归时还需要根据翻转次数判断当前应该从后往前遍历还是从前往后遍历。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 5e2;
int n, pos[N];
string s;
stack<int> st;
char get(char c, int change) {
if (change) {
if (c >= 'a' && c <= 'z') return c - 32;
return c + 32;
}
return c;
}
void dfs(int left, int right, int dir, int change) {
if (left > right) return;
if (dir) {
for (int i = left; i <= right; i++) {
if (s[i] == '(') {
dfs(i + 1, pos[i] - 1, dir ^ 1, change ^ 1);
i = pos[i];
} else {
cout << get(s[i], change);
}
}
} else {
for (int i = right; i >= left; i--) {
if (s[i] == ')') {
dfs(pos[i] + 1, i - 1, dir ^ 1, change ^ 1);
i = pos[i];
} else {
cout << get(s[i], change);
}
}
}
}
void solve() {
cin >> s;
n = s.size();
s = "$" + s;
for (int i = 1; i <= n; i++) {
if (s[i] == '(') {
st.push(i);
}
if (s[i] == ')') {
pos[i] = st.top();
pos[st.top()] = i;
st.pop();
}
}
dfs(1, n, 1, 0);
}
int main() {
solve();
return 0;
}
G.Mediator(bitset,分治)
题意
Tips: 本题的输入方式特殊,且内存限制较小
有一张包含 N N N个点的图,将对这个图进行 Q Q Q次操作,每次操作为以下两种操作之一:
-
1 u v
:在点 u u u和点 v v v之间建一条边 -
2 u v
:检查是否存在某个点 a a a,同时满足 a a a和 u u u直接连接,且 a a a和 v v v直接连接,如果有,返回这个点的编号,没有,返回0
开始时,有 X = 0 X = 0 X=0。
每行将输入三个正整数
a
,
b
,
c
a, b, c
a,b,c,你需要通过以下计算得到正确的操作A B C
:
-
A = 1 + ( ( ( a × ( 1 + X ) ) m o d 998244353 ) m o d 2 ) A = 1 + (((a \times (1 + X)) mod \text{ } 998244353) mod \text{ } 2) A=1+(((a×(1+X))mod 998244353)mod 2)
-
B = 1 + ( ( ( b × ( 1 + X ) ) m o d 998244353 ) m o d 2 ) B = 1 + (((b \times (1 + X)) mod \text{ } 998244353) mod \text{ } 2) B=1+(((b×(1+X))mod 998244353)mod 2)
-
C = 1 + ( ( ( c × ( 1 + X ) ) m o d 998244353 ) m o d 2 ) C = 1 + (((c \times (1 + X)) mod \text{ } 998244353) mod \text{ } 2) C=1+(((c×(1+X))mod 998244353)mod 2)
每次操作2结束后, X X X会变为操作2查询的结果。
分析
考虑使用bitset
记录每个点连接的点的信息,即使用
b
t
[
i
]
[
j
]
=
0
bt[i][j] = 0
bt[i][j]=0表示点
i
i
i有一条连接点
j
j
j的边,
b
t
[
i
]
[
j
]
=
0
bt[i][j] = 0
bt[i][j]=0表示没有。
那么每次想知道同时连接点 u u u和点 v v v的点时,可以直接计算 b t [ u ] bt[u] bt[u] ^ b t [ v ] bt[v] bt[v],如果结果不为全 0 0 0的二进制串,那么找到那个 1 1 1所在的位置即为答案,否则,就是不存在这个点。
而由于题目内存限制较小,如果直接使用bitset
会因为内存超限而无法通过,因此,需要进行优化。
将问题分成两部分,将编号较小的点使用bitset
进行维护,将编号较大的点使用vector
进行维护。
这样,就能保证时间和空间均能在题目规定范围内完成。
Tips
-
通过计算可以发现,只要
bitset
的长度小于等于 2 × 1 0 4 2 \times 10^{4} 2×104,那么就不会出现内存超限 -
本题卡了
bitset
的内存,但没考虑卡掉vector
模拟的时间,因此直接使用vector
进行模拟可以通过
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5e2, M = 1e4;
const int mod = 998244353;
int n, q, vis[N];
ll x;
bitset<M> bt[N];
vector<int> G[N];
void solve() {
ll a, b, c;
cin >> a >> b >> c;
a = 1 + ((a * (1 + x)) % mod) % 2;
b = 1 + ((b * (1 + x)) % mod) % n;
c = 1 + ((c * (1 + x)) % mod) % n;
if (a == 1) {
if (c >= M) G[b].push_back(c);
else bt[b].set(c);
if (b >= M) G[c].push_back(b);
else bt[c].set(b);
} else {
auto num = bt[b] & bt[c];
if (num.count())x = num._Find_first();
else {
x = 0;
memset(vis, 0, sizeof(vis));
for (auto i : G[b]) vis[i] = 1;
for (auto j : G[c]) if (vis[j]) {
x = j;
break;
}
}
cout << x << endl;
}
}
int main() {
cin >> n >> q;
while (q--) {
solve();
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。