A. Penalty Kick(循环)
题意
有
n
n
n轮比赛,已知在第
i
i
i轮比赛中,如果
i
i
i是
3
3
3的倍数,那么将会输掉这场比赛,否则将会赢下这场比赛,请你输出一个序列表示每轮比赛的结果(o
表示赢,x
表示输)。
分析
使用循环进行判断输出即可。
代码
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
if (i % 3 == 0) {
cout << "x";
} else {
cout << "o";
}
}
}
int main() {
solve();
return 0;
}
B.Farthest Point(枚举)
题意
给出二维坐标系上的 N N N个点,请你输出每个点距离最远的点的编号是什么(只比较与其他 N − 1 N - 1 N−1个点的距离,如果存在多个距离相等的点,输出编号最小的一个)。
分析
对于每个点,枚举其他的 N − 1 N - 1 N−1个点,记录距离最远的点即可。
代码
#include<bits/stdc++.h>
using namespace std;
int x[105], y[105];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];
for (int i = 1; i <= n; i++) {
int len = -1, id = i;
for (int j = 1; j <= n; j++) {
int l = (x[j] - x[i]) * (x[j] - x[i]) + (y[j] - y[i]) * (y[j] - y[i]);
if (l > len) {
len = l;
id = j;
}
}
cout << id << endl;
}
}
int main() {
solve();
return 0;
}
C.Colorful Beans(map)
题意
有 N N N种糖豆,每种糖豆都有一个美味值以及一个颜色,由于所有豆子都被混在一起,我们只能根据颜色来分辨出不同的豆子。
请你选出一种颜色的一个豆子,问能获得的最大的最小美味值是多少。
分析
考虑最坏情况,即无论选择哪种颜色的豆子,被选出的豆子均为该颜色豆子中美味值最小的一个,那么可以将所有颜色的豆子均视为只存在美味值最低的那一颗,然后统计所有颜色豆子美味值最低的那一颗中的最大值即可,由于数据规模较大,可以使用map
对颜色和美味值进行存储。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll, ll> P;
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
ll a, c;
cin >> a >> c;
if (P.find(c) == P.end()) {
P[c] = a;
} else {
P[c] = min(P[c], a);
}
}
ll maxn = -1;
for (auto i : P) {
maxn = max(maxn, i.second);
}
cout << maxn << endl;
}
int main() {
solve();
return 0;
}
D.Medicines on Grid(DFS)
题意
有一个 H × W H \times W H×W的网格,网格中每个 1 × 1 1 \times 1 1×1的小方格中包含以下四种内容之一:
-
'.'
:表示空格子 -
'#'
:表示存在一个障碍物 -
'S'
:表示起点 -
'T'
:表示终点
每次移动到四方向上相邻的格子中,均会消耗 1 1 1点能量,当能量为 0 0 0时,将不能再移动。
在网格中,其中 N N N个格子中包含着药品,使用第 i i i个药品会将能量值设置为 E i E_i Ei。他只能在这个药品对应的格子上使用药品,不能将药品带出格子,且药品使用后就会消失。
问:以 0 0 0点初始能量从起点出发,能否到达终点。
分析
本题看上去是一个经典的DFS
问题,但数据规模较大,如果想要使用DFS
算法通过,需要优化掉很多多余的操作。
优化1:inline关键字
在DFS
函数前加上inline
关键字,降低函数调用开销
inline void dfs(...) {
...
}
优化2:找到答案后就不需要搜索了
由于题目只问是否有解,那么可以使用变量
f
l
a
g
flag
flag记录是否找到过答案,到达终点后将
f
l
a
g
flag
flag标记为
1
1
1,并在
d
f
s
dfs
dfs开头进行判断,如果
f
l
a
g
=
1
flag = 1
flag=1,直接return
,不需要继续查找。
Tips: 本优化在极限数据下无效
优化3:当前解不可能为最优解的就排除掉
对于多次到达某个点,可以想到,如果之前到达点
(
i
,
j
)
(i, j)
(i,j)的剩余能量更多,那么我们当前继续走下去必然不会比前面方案更优,因此,可以使用一个
m
a
x
n
maxn
maxn数组,使用
m
a
x
n
[
i
]
[
j
]
maxn[i][j]
maxn[i][j]表示曾经到达点
(
i
,
j
)
(i, j)
(i,j)的最大能量,并在dfs开头判断,如果当前走到这个点的能量不如记录的
m
a
x
n
[
i
]
[
j
]
maxn[i][j]
maxn[i][j],直接return
。
优化4:去掉多余判断
由于网格上进行搜索需要判断是否出界和点是否为障碍物,因此需要额外的5条判断代码,我们可以想办法把这5条代码优化掉。
将maxn
数组初始化为极大值,然后将所有能走的点('.', 'S'. 'T'
)修改为-1,同时将坐标均设置为从
1
1
1开始,即行为
1
∼
h
1 \sim h
1∼h,列为
1
∼
w
1 \sim w
1∼w,那么所有不能走的点的maxn数组记录的值均为极大值,如果走到该点就会直接return
,不再需要对边界以及不能走的点进行判断。
代码(可以在C++20下通过)
#include<bits/stdc++.h>
using namespace std;
int h, w, fx, fy;
char c[305][305];
int e[305][305], vis[305][305];
int flag;
inline void dfs(int x, int y, int cnt) {
if (flag || cnt <= vis[x][y]) return;
if (c[x][y] == 'T') {
flag = 1;
return;
}
cnt = cnt < e[x][y] ? e[x][y] : cnt;
vis[x][y] = cnt;
if (!cnt) return;
int num = e[x][y];
e[x][y] = 0;
dfs(x + 1, y, cnt - 1);
dfs(x - 1, y, cnt - 1);
dfs(x, y - 1, cnt - 1);
dfs(x, y + 1, cnt - 1);
e[x][y] = num;
}
void solve() {
cin >> h >> w;
memset(vis, 0x3f, sizeof(vis));
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
cin >> c[i][j];
if (c[i][j] == 'S') {
fx = i;
fy = j;
}
if (c[i][j] != '#') {
vis[i][j] = -1;
}
}
}
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int r, C, E;
cin >> r >> C >> E;
e[r][C] = max(e[r][C], E);
}
dfs(fx, fy, 0);
if (flag == 1) cout << "Yes" << endl;
else cout << "No" << endl;
}
int main() {
solve();
return 0;
}
E.Minimize Sum of Distances(DFS)
题意
给出一个包含 N N N个点, N − 1 N - 1 N−1条边的无向图(保证给出的图是一棵树),图上第 i i i条边连接着点 a i a_i ai和 b i b_i bi。
给出一个序列 C = ( C 1 , C 2 , … , C N ) C = (C_1, C_2, \ldots, C_N) C=(C1,C2,…,CN), C i C_i Ci表示结点 i i i的权值。并定义 d ( a , b ) d(a, b) d(a,b)为点 a a a和 b b b之间的距离。并对于每个节点 x ( x = 1 , 2 , … , N ) x(x = 1, 2, \ldots, N) x(x=1,2,…,N),定义 f ( x ) = ∑ i = 1 N ( C i × d ( x , i ) ) f(x) = \sum\limits_{i = 1}^{N}(C_i \times d(x, i)) f(x)=i=1∑N(Ci×d(x,i)),请你找到 m i n 1 ≤ v ≤ N f ( v ) \mathop{min}\limits_{1 \le v \le N}f(v) 1≤v≤Nminf(v)。
分析
假设以节点 k k k为根节点,那么 f ( k ) f(k) f(k)实际上就是 ∑ i = 1 N ( C i × d e e p [ i ] ) \sum\limits_{i = 1}^{N}(C_i \times deep[i]) i=1∑N(Ci×deep[i])(根节点深度为0),因此每个点到达根节点的距离就等于深度。
然后考虑树上每个节点 x x x的 f ( x ) f(x) f(x)是多少,不难想到,当从子树的根节点走向某个子结点时,与走向的子结点中包含的所有结点的距离均会减少 1 1 1,而与树上其他所有结点的距离都会增加 1 1 1,因此,可以使用数组 s u m C [ i ] sum_C[i] sumC[i]记录以 i i i为根节点的子树的节点权值之和,而整棵树的权值之和减去该子树的权值之和即为需要加上的部分,即:
- f ( v ) = f ( u ) − s u m C [ v ] + s u m C [ 1 ] − s u m C [ v ] = f ( u ) + s u m C [ 1 ] − 2 ∗ s u m C [ v ] ; f(v) = f(u) - sum_C[v] + sum_C[1] - sum_C[v] = f(u) + sum_C[1] - 2 * sum_C[v]; f(v)=f(u)−sumC[v]+sumC[1]−sumC[v]=f(u)+sumC[1]−2∗sumC[v];(这里以结点1作为根节点,v为u的直接子结点)
任选一个结点建树,并维护
s
u
m
C
sum_C
sumC数组,然后再使用一次dfs
,枚举所有树上的点,记录最小的
f
(
i
)
f(i)
f(i)即为最后的答案。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5e2;
vector<int> G[N];
ll n, deep[N], C[N], sum_C[N], ans;
void dfs(int u, int pre) {
ans += deep[u] * C[u];
sum_C[u] = C[u];
for (auto v : G[u]) {
if (v == pre) continue;
deep[v] = deep[u] + 1;
dfs(v, u);
sum_C[u] += sum_C[v];
}
}
void dfs2(int u, int pre, ll sum) {
for (auto v : G[u]) {
if (v == pre) continue;
ll cnt = sum + sum_C[1] - 2 * sum_C[v];
ans = min(ans, cnt);
dfs2(v, u, cnt);
}
}
void solve() {
cin >> n;
for (int i = 2; i <= n; i++) {
int a, b;
cin >> a >> b;
G[a].push_back(b);
G[b].push_back(a);
}
for (int i = 1; i <= n; i++) cin >> C[i];
dfs(1, -1);
dfs2(1, -1, ans);
cout << ans << endl;
}
int main() {
solve();
return 0;
}
F.Oddly Similar(bitset)
题意
给出 N N N个包含 M M M个元素的序列,如果两个序列 X X X和 Y Y Y满足相同下标上相同的数字数量为奇数的话,那么就认为这两个序列是相似的,问:给出的 N N N个序列中,有多少对序列是相似的。
分析
考虑使用 b i t s e t bitset bitset进行优化,使用 b t [ i ] [ j ] [ k ] bt[i][j][k] bt[i][j][k]表示第 i i i列数字 j j j在第 k k k行的状态,当 b t [ i ] [ j ] [ k ] = 1 bt[i][j][k] = 1 bt[i][j][k]=1表示第 k k k行第 i i i列上的数字为 j j j。
那么为什么要这么做呢?
首先,要想知道前面的 1 ∼ k − 1 1 \sim k - 1 1∼k−1行有多少个数字与当前这一行的第 i i i列的数字相同,那么只需要检查 b t [ i ] [ j ] bt[i][j] bt[i][j]表示的二进制串上有多少个 1 1 1即可。
怎么判断前面所有行中多少行相同下标的数字数量为奇数呢?
使用一个 b i t s e t bitset bitset T T T表示第 k k k行与前面 k − 1 k - 1 k−1行的相似情况,即使用 T [ x ] = 1 T[x] = 1 T[x]=1表示第 x x x行与第 k k k行相似。由于奇数个相同才算相似,因此,让每列相同的行状态二进制串 b t [ i ] [ j ] bt[i][j] bt[i][j]与 T T T进行异或运算,那么结束运算后,如果某行与第 k k k行有奇数个想同下标相同的数字,那么结果的二进制串中对应位置必然为 1 1 1,否则为 0 0 0。
每轮记录 T T T中包含多少个 1 1 1,即为当前第 k k k行与前面 k − 1 k - 1 k−1行有多少行相似。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2005, M = 1005;
int n, m, a[N][N];
bitset<N> T, bt[N][M];
void solve() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
T.reset();
for (int j = 0; j < m; j++) {
T ^= bt[j][a[i][j]];
bt[j][a[i][j]].set(i);
}
ans += T.count();
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。