A.Print 341(模拟)
题意:
给定一个正整数
N
N
N,输出由
N
N
N个0
和
(
N
+
1
)
(N+1)
(N+1)个1
交替组成的字符串。
分析:
按题意模拟即可
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
cout << 1;
for (int i = 0; i < n; i++) {
cout << "01";
}
cout << endl;
return 0;
}
B.Foreign Exchange(贪心)
题意:
有 N N N个国家,编号为 1 1 1到 N N N。对于每个 i = 1 , 2 , … , N i=1,2,…,N i=1,2,…,N,高桥拥有 A i A_i Ai个第 i i i个国家的货币。高桥可以重复执行以下操作任意次数(可能为零):
- 首先,在 1 1 1和 N − 1 N-1 N−1(含两端)之间选择一个整数 i i i。然后,如果他拥有至少 S i S_i Si个第 i i i个国家的货币,则他会执行以下操作一次:用 S i S_i Si个第 i i i国家的货币交换 T i T_i Ti个下一个国家 ( i + 1 ) (i+1) (i+1)的货币。
计算高桥最终可能拥有的第 N N N个国家货币数量的最大值。
分析:
按照题意,按照题目所给规则一国一国模拟兑换即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;
LL a[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n - 1; i++) {
int s, t;
cin >> s >> t;
a[i + 1] += a[i] / s * t;
}
cout << a[n - 1] << endl;
return 0;
}
C.Takahashi Gets Lost(模拟)
题意:
有一个
H
H
H行
W
W
W列的网格。网格的每个单元格都是陆地或海洋,由长度为
W
W
W的
H
H
H个字符串
S
1
,
S
2
,
…
,
S
H
S_1,S2,…,S_H
S1,S2,…,SH表示。
(
i
,
j
)
(i,j)
(i,j)表示从上往下第
i
i
i行和从左往右第
j
j
j列的单元格,并且如果
S
i
S_i
Si的第
j
j
j个字符是.
,则
(
i
,
j
)
(i,j)
(i,j)是陆地;如果该字符是#
,则
(
i
,
j
)
(i,j)
(i,j)是海洋。
题目约束保证网格周边(即满足 i = 1 i=1 i=1、 i = H i=H i=H或 j = 1 j=1 j=1、 j = W j=W j=W中至少一项的单元格 ( i , j ) (i,j) (i,j))上所有单元都是海洋。
T a k a h a s h i Takahashi Takahashi的飞船在网格中某个单元格坠毁了。之后,他按照长度为 N N N的字符串 T T T中表示的指令移动 N N N次,其中包括 L L L、 R R R、 U U U和 D D D 。对于 i = 1 , 2 , … , N i=1,2,…,N i=1,2,…,N, T T T的第 i i i个字符描述如下:
- L L L表示向左移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i , j − 1 ) (i,j−1) (i,j−1)处;
- R R R表示向右移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i , j + 1 ) (i,j+1) (i,j+1)处;
- U U U表示向上移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i − 1 , j ) (i−1,j) (i−1,j)处;
- D D D表示向下移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i + 1 , j ) (i+1,j) (i+1,j)处。
已知沿着路径的所有单元格(包括坠毁并且当前所在位置)都不会被淹没,输出可能成为其当前位置的单元格数目。
分析:
题目要求按指令移动时不会经过海洋,即不会经过#
。枚举位置,模拟操作序列,判断是否会经过#
即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 505;
char c[N][N];
int main() {
int h, w, n;
cin >> h >> w >> n;
string t;
cin >> t;
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
cin >> c[i][j];
}
}
int ans = 0;
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
if (c[i][j] == '#')
continue;
int a = i, b = j;
bool check = true;
for (int k = 0; k < n; k++) {
if (t[k] == 'L') {
if (b == 1) {
check = false;
break;
}
b -= 1;
if (c[a][b] == '#') {
check = false;
break;
}
} else if (t[k] == 'U') {
if (a == 1) {
check = false;
break;
}
a -= 1;
if (c[a][b] == '#') {
check = false;
break;
}
} else if (t[k] == 'R') {
if (b == w) {
check = false;
break;
}
b += 1;
if (c[a][b] == '#') {
check = false;
break;
}
} else {
if (a == h) {
check = false;
break;
}
a += 1;
if (c[a][b] == '#') {
check = false;
break;
}
}
}
if (check) {
ans++;
}
}
}
cout << ans << endl;
return 0;
}
D.Only one of two(二分)
题意:
给定三个正整数 N N N、 M M M和 K K K。其中, N N N和 M M M不同。
输出第 K K K小的正整数,该正整数恰好可被 N N N或 M M M中的一个数整除。
分析:
先找到 n , m n,m n,m的最小公倍数 l l l,那么对于一个数 x x x,能被 n n n整除且小于等于 x x x的数的个数就是 [ x / n ] [x/n] [x/n],所以可以推出(因为可能同时能被 n , m n,m n,m整除,要删掉能被 l l l整除的数字个数)
[ x / n ] + [ x / m ] − 2 ∗ [ x / l ] > = k [x/n]+[x/m]-2*[x/l]>=k [x/n]+[x/m]−2∗[x/l]>=k
使用二分来进行查找即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
int main() {
LL n, m, x, k;
cin >> n >> m >> k;
x = (n * m) / gcd(n, m);
LL l = 0, r = (LL) 2e+20;
LL mid, cnt;
while ((l + 1) < r) {
mid = (l + r) / 2;
cnt = (mid / n) + (mid / m) - 2 * (mid / x);
if (cnt < k)
l = mid;
else
r = mid;
}
cout << r << endl;
return 0;
}
E.Alternating String(线段树)
题意:
定义好字符串是由0
和1
组成的字符串,其中两个连续字符总是不同的。
给你一个长度为
N
N
N的字符串
S
S
S,由0
和1
组成。
给出
Q
Q
Q个查询,必须按顺序处理。
查询有两种类型:
1 L R
:翻转 S S S中第 L L L到第 R R R的每个字符。也就是说,对于每个满足 L ≤ i ≤ R L\leq i\leq R L≤i≤R的整数 i i i,如果 S S S中的第 i i i个字符是"1",则将其改为0
,反之亦然。2 L R
:假设 S ′ S' S′是通过提取 S S S的第 L L L到第 R R R个字符(不改变顺序)得到的长度为 ( R − L + 1 ) (R-L+1) (R−L+1)的字符串。如果 S ′ S' S′是一个好字符串,则输出Yes
,否则输出No
。
分析:
题目要求的好字符串必须是01
交替出现。设数组
x
i
=
(
s
i
⊕
s
i
+
1
)
x_i=(s_i \oplus s_{i+1})
xi=(si⊕si+1),如果
∑
i
=
l
r
−
1
x
i
=
=
r
−
l
\sum\limits_{i=l}^{r-1}x_i==r-l
i=l∑r−1xi==r−l,那说明
S
S
S的
[
l
,
r
]
[l,r]
[l,r]是01
交替的。对区间
[
l
,
r
]
[l,r]
[l,r]进行翻转操作,不难发现所有的
x
i
x_i
xi都不会发生变化。发生变化的只有两个边界,即
x
l
−
1
x_{l-1}
xl−1和
x
r
x_r
xr,显然这两个值会变成它们相反的数。我们注意到操作一对该数组的影响是两个单点修改,操作二是一个区间查询,用线段树维护数组即可。
代码:
#include<bits/stdc++.h>
using namespace std;
template<typename T>
class settree {
public:
vector<T> cnt;
int n;
settree(int _n) : n(_n) { cnt.resize(n); }
void modify(int x, T v) {
while (x < n) {
cnt[x] += v;
x |= (x + 1);
}
}
T get(int x) {
T v{};
while (x >= 0) {
v += cnt[x];
x = (x & (x + 1)) - 1;
}
return v;
}
T sum(int l, int r) {
T tot = get(r);
if (l != 0) {
tot -= get(l - 1);
}
return tot;
}
};
int main() {
int n, q;
cin >> n >> q;
string s;
cin >> s;
settree<int> sum(n - 1);
for (int i = 0; i < n - 1; i++) {
sum.modify(i, s[i] != s[i + 1]);
}
while (q--) {
int qu, l, r;
cin >> qu >> l >> r;
if (qu == 1) {
--l, --r;
if (l != 0) {
sum.modify(l - 1, sum.sum(l - 1, l - 1) == 1 ? -1 : 1);
}
if (r != (n - 1)) {
sum.modify(r, sum.sum(r, r) == 1 ? -1 : 1);
}
} else {
--l, --r;
if (sum.sum(l, r - 1) == (r - l))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
return 0;
}
F.Breakdown(01背包)
题意:
给你一个由 N N N个顶点和 M M M条边组成的简单无向图。对于 i = 1 , 2 , … , M i=1,2,\ldots,M i=1,2,…,M来说,第 i i i条边连接顶点 u i u_i ui和 v i v_i vi。另外,对于 i = 1 , 2 , … , N i=1,2,\ldots,N i=1,2,…,N来说,顶点 i i i被赋值为正整数 W i W_i Wi,并且有 A i A_i Ai个碎片放在上面。
只要图上还有碎片,就重复下面的操作:
- 首先,从图形中选择一个点 x x x并移除上面的一个碎片。
- 选择与 x x x相邻的顶点集合 S S S(可能为空),即 ∑ y ∈ S W y < W x \sum\limits_{y\in S}W_y\lt W_x y∈S∑Wy<Wx,并在 S S S中的每个顶点上放置一个碎片。
输出此操作的最大次数。
题目保证无论如何操作,在有限次迭代后,图形上将没有棋子。
分析:
注意到操作里存在方向性即碎片总是往点权小的点跑。可以先求权值小的点,考虑权值大的点时,考虑放置碎片的点的答案都已经求出,因此可以求出该点的答案。
按点权小到大的顺序求解,假设 d p i dp_i dpi表示点 i i i上有一个碎片带来的最大操作次数,考虑求解当前点 d p i dp_i dpi时,就是考虑其 w y ≤ w i w_y \le w_i wy≤wi的所有 y y y。选哪些 y y y,其 ∑ d p y \sum dp_y ∑dpy最大。
可以发现这样考虑本题即为01背包问题,因此就按照点权小到大的顺序求解 n n n个01背包问题即可。
最后答案就是 ∑ ( d p i × a i ) \sum (dp_i \times a_i) ∑(dpi×ai)。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> edge(n);
int u, v;
for (int i = 0; i < m; i++) {
cin >> u >> v;
u--, v--;
edge[u].push_back(v);
edge[v].push_back(u);
}
vector<int> w(n);
for (auto &x: w)
cin >> x;
vector<int> a(n);
for (auto &x: a)
cin >> x;
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int x, int y) { return w[x] < w[y]; });
vector<int> cost(n);
LL ans = 0;
for (auto i: id) {
vector<array<int, 2>> goods;
for (auto j: edge[i]) {
if (w[j] < w[i])
goods.push_back({w[j], cost[j]});
}
if (goods.empty()) {
cost[i] = 1;
} else {
vector<int> dp(w[i], 0);
for (auto &[x, y]: goods) {
for (int j = w[i] - 1; j >= x; j--) {
dp[j] = max(dp[j], dp[j - x] + y);
}
}
cost[i] = 1 + *max_element(dp.begin(), dp.end());
}
ans += 1LL * a[i] * cost[i];
}
cout << ans << endl;
return 0;
}
G.Highest Ratio(计算几何)
题意:
给你一个长度为
N
N
N的序列
A
=
(
A
1
,
A
2
,
…
,
A
N
)
A=(A_1,A_2,\ldots,A_N)
A=(A1,A2,…,AN)。
求每个
k
=
1
,
2
,
…
,
N
k=1,2,\ldots,N
k=1,2,…,N的解:
- 当选择一个整数 r r r使得 k ≤ r ≤ N k\leq r\leq N k≤r≤N时,求序列 A A A中第 k k k到第 r r r项的最大平均值。
序列 A A A的第 k k k到第 r r r项的平均值定义为 1 r − k + 1 ∑ i = k r A i \frac{1}{r-k+1}\displaystyle\sum\limits_{i=k}^r A_i r−k+11i=k∑rAi。
分析:
考虑二维平面上编号为 0 , 1 , 2 , … , N 0,1,2,\ldots,N 0,1,2,…,N的 ( N + 1 ) (N+1) (N+1)个点。
点
0
0
0的坐标为
(
x
0
,
y
0
)
=
(
0
,
0
)
(x_0,y_0)=(0,0)
(x0,y0)=(0,0),点
i
i
i的坐标为
(
x
i
,
y
i
)
=
(
i
,
A
1
+
A
2
+
⋯
+
A
i
)
(x_i,y_i)=(i,A_1+A_2+\cdots +A_i)
(xi,yi)=(i,A1+A2+⋯+Ai)。
那么
A
L
,
A
L
+
1
,
…
,
A
R
A_L,A_{L+1},\ldots,A_R
AL,AL+1,…,AR的平均值就表示为点
L
−
1
{L-1}
L−1和点
R
R
R的斜率。
对于固定的
k
k
k
(
1
≤
k
≤
N
)
(1\leq k\leq N)
(1≤k≤N),考虑如何求出序列
A
A
A中第
k
k
k到第
r
r
r项的最大平均值。
取点
k
−
1
,
k
,
k
+
1
,
…
,
N
{k-1},k,k+1,\ldots,N
k−1,k,k+1,…,N中的凸包。如果点
k
−
1
,
k
,
k
+
1
,
…
,
N
{k-1},k,k+1,\ldots,N
k−1,k,k+1,…,N是共线的,那么是
A
k
=
A
k
+
1
=
⋯
A
N
A_k=A_{k+1}=\cdots~A_N
Ak=Ak+1=⋯ AN,所以平均值是
A
k
A_k
Ak。凸包是一个面积为正的区域。点
k
−
1
{k-1}
k−1是唯一一个坐标最小为
x
x
x的点,位于凸包的边界上。从点
(
k
−
1
)
(k-1)
(k−1)顺时针绕边界一周后,如果下一个点是
p
p
p,那么答案就是
y
p
−
y
k
−
1
x
p
−
x
k
−
1
\frac{y_p-y_{k-1}}{x_p-x_{k-1}}
xp−xk−1yp−yk−1。这是因为,从点
(
k
−
1
)
(k-1)
(k−1)逆时针时,如果下一个点是
q
q
q,那么对于构成凸包的点
i
i
i
(
k
≤
i
≤
N
)
(k\leq i\leq N )
(k≤i≤N),答案是
y
p
−
y
k
−
1
x
p
−
x
k
−
1
\frac{y_p-y_{k-1}}{x_p-x_{k-1}}
xp−xk−1yp−yk−1。根据凸包的定义,经过点
i
i
i和
(
k
−
1
)
(k-1)
(k−1)的直线的斜率至少是点
q
q
q和点
(
k
−
1
)
(k-1)
(k−1)的斜率,最多是点
p
p
p和
(
k
−
1
)
(k-1)
(k−1)的斜率。我们所求的值相当于连接点
i
i
i
(
k
≤
i
≤
N
)
(k\leq i\leq N)
(k≤i≤N)和点
(
k
−
1
)
(k-1)
(k−1)的直线的最大斜率。所以答案是
y
p
−
y
k
−
1
x
p
−
x
k
−
1
\frac{y_p-y_{k-1}}{x_p-x_{k-1}}
xp−xk−1yp−yk−1,也就是连接点
p
p
p和
(
k
−
1
)
(k-1)
(k−1)的直线
(
k
−
1
)
(k-1)
(k−1)的斜率。
毕竟,对于每个点 k k k,只需考虑点 k − 1 , k + 1 , … , N {k-1},k+1,\ldots,N k−1,k+1,…,N的凸包,并找出从点 ( k − 1 ) (k-1) (k−1)顺时针方向遍历凸包边界时遇到的下一个点即可;换句话说,就是构成上边界的点序列中点 ( k − 1 ) (k-1) (k−1)的下一个点。
如果直接为每个 k k k寻找凸包,会超时。但是,在按此顺序添加点 N , N − 1 , … , 0 N,N-1,\ldots,0 N,N−1,…,0的同时求凸包上边界时,可以求出经过点 k − 1 {k-1} k−1的直线的斜率,以及构成上边界的点序列中点 k − 1 {k-1} k−1旁边的点。这样,就可以找到所有所需的值,其时间复杂度与只找到一次由点 0 , 1 , … , N 0,1,\ldots,N 0,1,…,N组成的点集的凸包的时间复杂度相同。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 2e5 + 5;
LL pre[N], arr[N], q[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
reverse(arr + 1, arr + n + 1);
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + arr[i];
}
vector<double> ans;
int l = 0, r = -1;
q[++r] = 0;
for (int i = 1; i <= n; i++) {
while (l < r && (pre[i] - pre[q[r]]) * (q[r] - q[r - 1]) <= (pre[q[r]] - pre[q[r - 1]]) * (i - q[r]))
r--;
ans.push_back(1.0 * (pre[i] - pre[q[r]]) / (i - q[r]));
q[++r] = i;
}
for (auto it = ans.rbegin(); it != ans.rend(); ++it) {
cout << fixed << setprecision(10) << *it << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。