A. AtCoder Line(判断)
题意
有 N N N个车站,列车的运行有两种方向,即 1 → n 1 \rightarrow n 1→n和 n → 1 n \rightarrow 1 n→1两种。
问,从 x x x点到 y y y点,仅乘坐一次列车,是否会经过站点 z z z。
分析
判断 z z z点是否出现在 x x x点到达 y y y点的路线中即可。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
int n, x, y, z;
cin >> n >> x >> y >> z;
if (x > y) {
if (z >= y && z <= x) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
} else {
if (z <= y && z >= x) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
int main() {
solve();
return 0;
}
B.Typing(字符串匹配)
题意
有两个字符串,一个想要打出的字符串S
,一个是实际打出字符串T
,请你输出当前字符串T
中正确字符所在的下标。
分析
进行双指针进行字符串匹配,每次匹配上后输出下标即可。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
string s, t;
cin >> s >> t;
int len_s = s.size(), len_t = t.size();
for (int i = 0, j = 0; i < len_t; i++) {
if (s[j] != t[i]) {
} else {
cout << i + 1 << ' ';
j++;
}
}
}
int main() {
solve();
return 0;
}
C. Standing On The Shoulders(枚举)
题意
有 n n n个人,每个人有一个肩膀的高度以及头顶的高度。
你可以任意重排这 n n n个人,排序完成后,第一个人将站在地面上,后面的每一个人均站在前一个人的肩膀上,问,最优方案下,最高的人的头顶距离地面的最高高度是多少?
分析
观察发现,前 n − 1 n - 1 n−1个人对最后高度的贡献只有他的肩膀高度,而最后一个人对最后高度的贡献只有他的头顶高度,因此,可以先统计所有人的肩膀高度之和,然后依次枚举每个人作为最后一个人的高度即可(即肩膀高度之和减去当前人的肩膀高度再加上头部高度)。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node {
ll x, y;
} a[200005];
void solve() {
int n;
cin >> n;
ll sum = 0;
for (int i = 0; i < n; i++) {
cin >> a[i].x >> a[i].y;
sum += a[i].x;
}
ll ans = 0;
for (int i = 0; i < n; i++) {
ans = max(ans, sum - a[i].x + a[i].y);
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
D. Permutation Subsequence(滑动窗口+set)
题意
给出一个包含 1 ∼ n 1 \sim n 1∼n的一个排列,当你从这个排列中取出其中 k k k个数字,如果这 k k k个数字满足以下条件,那么就是一个好的序列:
-
取出的数字下标分别为 i 1 , i 2 , … , i k i_1, i_2, \ldots, i_k i1,i2,…,ik,且 i i < i 2 < … < i k i_i < i_2 < \ldots < i_k ii<i2<…<ik
-
将取出的 k k k个数字进行排序后,第一个数字为 a a a,后面的数字依次为 a + 1 , a + 2 , a + 3 , … , a + k − 1 a + 1, a + 2, a + 3, \ldots, a + k - 1 a+1,a+2,a+3,…,a+k−1。
请你求出所有好的序列中, i k − i 1 i_k - i_1 ik−i1的最小值。
分析
不难想到,所有好的序列必然是将原数组排序,任取其中连续的 k k k个数字,那么难点就在于快速得到每一段区间对应的数字原本下标的最大最小值(数组同时记录数字以及数字开始时的下标)。
如果掌握线段树等计算区间极值的算法,就可以直接枚举区间计算答案。
以下给出一个特殊做法,固定一个长度为
k
k
k的窗口,并使用两个set
维护区间内的数字所在的原下标,两个set
一个从小到大排序,一个从大到小排序。这样,每次移动完窗口后,只需要取出两个set
的第一个元素,就能知道区间内数字原本下标的最大最小值,每次更新区间的时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。
这样,记录所有窗口中的最大下标减最小下标的最小值即可。
Hint
可以通过
∗
(
s
e
t
.
b
e
g
i
n
(
)
)
*(set.begin())
∗(set.begin())获取set
中的第一个元素
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node{
ll a, id;
}a[200005];
ll n, k;
set<int, greater<int> > st1;
set<int, less<int> > st2;
void solve() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i].a;
a[i].id = i;
}
int ans = INT_MAX;
sort(a + 1, a + n + 1, [&](Node o, Node o1) {
if (o.a != o1.a) return o.a < o1.a;
return o.id < o1.id;
});
for (int i = 1; i <= k; i++) {
st1.insert(a[i].id);
st2.insert(a[i].id);
}
ans = *(st1.begin()) - *(st2.begin());
for (int i = k + 1; i <= n; i++) {
st1.erase((a[i - k].id));
st1.insert(a[i].id);
st2.erase((a[i - k].id));
st2.insert(a[i].id);
ans = min(ans, *(st1.begin()) - *(st2.begin()));
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
E.Clique Connect(并查集)
题意
给出一个包含 n n n个点的图,你将会对这个图进行以下操作 M M M次:
- 给出一个集合 S S S,以及一个费用 C C C,将所有属于集合中的点两两建边,所有边的边权均为 C C C.
完成操作后,问,这个图上的最小生成树的权值是多少?
分析
本题是并查集模板题。
由于每次对一个集合中建的边权都是相同的,那么不需要记录所有的边,只要要保证这个集合中的点连通即可,这里选择将每个点与集合中前一个点建边。
然后就是经典的Kruscal
算法,将所有边按边权排序,然后按边权从小到达遍历边,并使用并查集进行维护,当两个点不在同一个集合中时,就在这两个点之间建边,并记录费用以及连的边数。
结束操作后,如果没有连上
n
−
1
n - 1
n−1条边,就说明不存在最小生成树,输出-1
。否则,输出记录的费用。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node{
ll u, v, cost;
friend bool operator < (const Node &o1, const Node &o2) {
return o1.cost < o2.cost;
}
}num[400005];
ll n, m, cnt, ans, sum, f[400005];
int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}
void merge(Node node) {
int fu = find(node.u);
int fv = find(node.v);
if (fu != fv) {
f[fu] = fv;
ans += node.cost;
sum++;
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int k, c;
cin >> k >> c;
int pre = -1;
for (int j = 1; j <= k; j++) {
int a;
cin >> a;
if (j > 1) {
num[cnt++] = Node{pre, a, c};
}
pre = a;
}
}
sort(num, num + cnt);
for (int i = 1; i <= n; i++) f[i] = i;
for (int i = 0; i < cnt; i++) {
merge(num[i]);
}
if (sum != n - 1) {
cout << -1 << endl;
} else {
cout << ans << endl;
}
return 0;
}
F.Estimate Order(搜索)
题意:
有 N N N 人,编号为 1 1 1 至 N N N 。在这些 N N N 人中举行了一次竞赛,并对他们进行了相应的排名。有关他们的排名信息如下:
- 每个人都有一个唯一的排名。
- 对于每个 1 ≤ i ≤ M 1 \leq i \leq M 1≤i≤M ,如果 A i A_i Ai 的排名是 x x x -th,而 B i B_i Bi 的排名是 y y y -th,那么就是 x − y = C i x - y = C_i x−y=Ci 。
给定的输入保证了至少有一种可能的排名与给定的信息不矛盾。
回答
N
N
N 个查询。 第
i
i
i次查询的答案是一个整数,确定方法如下:
- 如果可以唯一确定人 i i i 的排名,则返回该排名。否则,返回 − 1 -1 −1 。
分析:
我们先将 m m m个条件按照排名差 c i c_i ci逆序排序,再对 m m m个条件进行 d f s dfs dfs,查找所有可能的方案。对于一种合法情况,记录其 n n n个点的排名。 d f s dfs dfs结束后,判断这 n n n个人的排名记录,如果存在某个人只有一个记录就是必定可以确定的。
代码:
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
using namespace std;
int n, m;
vector<pair<int, int>> g[20];
vector<vector<int>> team;
int vis[20];
int ans[20];
int visNum[20];
int num[20];
bool dfs1(int u, int k) {
if (k < 1 || k > n)
return false;
if (visNum[k])
return false;
visNum[k] = 1;
vis[u] = 1;
num[u] = k;
int flag = 1;
for (auto [v, w]: g[u]) {
if (vis[v])
continue;
flag &= dfs1(v, k - w);
}
return flag;
}
void dfs(int k) {
if (team[k].size() == 1) {
int rem = (int) team.size() - k;
if (rem > 1) {
for (int i = k; i < team.size(); i++) {
for (auto v: team[i]) {
ans[v] = 3;
}
}
for (int i = 1; i <= n; i++) {
ans[i] |= (1 << num[i]);
}
return;
}
}
if (k == team.size()) {
for (int i = 1; i <= n; i++) {
ans[i] |= (1 << num[i]);
}
return;
}
for (int i = 1; i <= n; i++) {
if (!visNum[i]) {
int flag = dfs1(team[k][0], i);
if (!flag) {
for (auto v: team[k])
num[v] = visNum[num[v]] = vis[v] = 0;
continue;
}
dfs(k + 1);
for (auto v: team[k])
num[v] = visNum[num[v]] = vis[v] = 0;
}
}
}
int main() {
cin >> n >> m;
while (m--) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, -w});
}
for (int i = 1; i <= n; i++) {
if (vis[i])
continue;
vector<int> t;
queue<int> q;
q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
if (vis[u])
continue;
vis[u] = 1;
t.push_back(u);
for (auto [v, w]: g[u]) {
if (vis[v])
continue;
q.push(v);
}
}
team.push_back(t);
}
memset(vis, 0, sizeof(vis));
sort(team.begin(), team.end(), [=](vector<int> a, vector<int> b) { return a.size() > b.size(); });
dfs(0);
for (int i = 1; i <= n; i++) {
if (__builtin_popcount(ans[i]) != 1)
cout << -1 << " ";
else {
for (int j = 1; j <= 16; j++) {
if (ans[i] >> j & 1)
cout << j << " ";
}
}
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。