AtCoder Beginner Contest 235
C - The Kth Time Query
题意:
现有包含 N 个的整数序列 A 以及 Q 次查询,每次查询从序列 A 中找到 x i x_i xi 第 k i k_i ki 的位置,若查询不到,则输出 -1。
题解:
知识点:排序加二分。 将原下标及 a i a_i ai封装结构体进行排序,键值为 a i a_i ai,二分查找 x i x_i xi 所出现的第一个位置及出现的最后一个位置加一位置,即lower_bound, upper_bound,若在序列 A 中未查询到 a i a_i ai ,则直接特判输出 - 1,否则将二分得到的区间与 k i k_i ki 相比较,超出范围则特判输出 - 1,则直接输出特定位置。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
struct Node {
int pos, val;
bool operator<(const Node &rhs) const{
if (val == rhs.val)
return pos < rhs.pos;
else
return val < rhs.val;
}
} a[N];
int n, q;
int lower_find(int x)
{
int l = 1, r = n + 1;
while (l < r) {
int mid = (l + r) >> 1;
if (x > a[mid].val)
l = mid + 1;
else
r = mid;
}
if (a[l].val == x)
return l;
else
return 0;
}
int upper_find(int x)
{
int l = 1, r = n + 1;
while (l < r) {
int mid = (l + r) >> 1;
if (x >= a[mid].val)
l = mid + 1;
else
r = mid;
}
if (a[l - 1].val == x)
return l;
else
return 0;
}
int main()
{
ios::sync_with_stdio(0);
cin >> n >> q;
for (int i = 1; i <= n; i ++) {
cin >> a[i].val;
a[i].pos = i;
}
sort (a + 1, a + n + 1);
int x, k;
vector<int> ans;
for (int i = 1; i <= q; i ++) {
cin >> x >> k;
int pos_l = lower_find(x);
int pos_r = upper_find(x);
if (!pos_l || !pos_r)
ans.push_back(-1);
else if (pos_l + k - 1 >= pos_r)
ans.push_back(-1);
else
ans.push_back(a[pos_l + k - 1].pos);
}
for (auto &it : ans)
cout << it << endl;
return 0;
}
D - Multiply and Rotate
题意:
给定初始值为 x = 1, 两种操作方式,第一种是将 x 替换为 a * x, 第二种是将 x 视为字符串,当前仅当满足 x 大于等于 10,x 不能被 10 整除, 将 x 的最右端字符放到最左端。求解 x = N 时的最小操作次数。
题解:
知识点:广度优先搜索。此题与三个杯子倒水问题相似,观察到 N 的范围只有 1e6 ,得知此题可用BFS来做,将 x 视作一个状态,不断的根据两个操作进行拓展即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7;
int a, n;
int level[N];
void BFS()
{
int array_int[10];
bool vis[N];
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(1);
level[1] = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
if (vis[u]) continue;
vis[u] = true;
if (u == n)
break;
if (1ll * a * u <= N) {
int v_int = a * u;
q.push(v_int);
level[v_int] = level[u] + 1;
}
if (u > 10 && u % 10) {
int m = 0, v_int = 0, tmp = u;
while (tmp) {
array_int[m ++] = tmp % 10;
tmp /= 10;
}
array_int[m] = array_int[0];
for (int i = m; i >= 1; i --)
v_int = 10 * v_int + array_int[i];
q.push(v_int);
level[v_int] = level[u] + 1;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin >> a >> n;
BFS();
if (level[n])
cout << level[n] << endl;
else
cout << "-1" << endl;
return 0;
}
E - MST + 1
题意:
给定一张带有边权的无向图,有自环重边,现有 Q 次询问给出三元组(u, v, w),意为从 u->v 边权为 w,对于每次查询将该边直接放入给定无向图中,判断生成的最小生成树中是否出现查询边。
题解:
知识点:克鲁斯卡尔算法。此题时克鲁斯卡尔算法转换思路,对于 Q 次查询每次都跑一边 Kruskal 时间复杂度为 O ( q n l o g n ) O(q\ n\ logn) O(q n logn) ,观察得在使用 Kruskal 建立最小生成树时有很多重复步骤,现可以直接将查询边放入至原图中直接跑 Kruskal ,但并不参与实际建树过程,未在同一连通分量中的点进行判断是否为查询边,若是则加入答案集合,否则直接连边。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
struct Node {
int from, to, val, pos;
bool tag;
bool operator<(const Node& rhs) const {
return val < rhs.val;
}
};
int n, m, q;
int f[N];
vector<Node> e;
set<int> se;
int find(int x)
{
return f[x] == x ? x : f[x] = find(f[x]);
}
void Kruskal()
{
for (int i = 1; i <= n; i ++)
f[i] = i;
int edge_size = e.size();
for (int i = 0; i < edge_size; i ++) {
int f1 = find(e[i].from), f2 = find(e[i].to);
if (f1 == f2)
continue;
if (e[i].tag) {
se.insert(e[i].pos);
} else {
f[f1] = f2;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin >> n >> m >> q;
int x, y, z;
for (int i = 1; i <= m; i ++) {
cin >> x >> y >> z;
e.push_back({x, y, z, 0, false});
}
for (int i = 1; i <= q; i ++) {
cin >> x >> y >> z;
e.push_back({x, y, z, i, true});
}
sort (e.begin(), e.end());
Kruskal();
set<int>::iterator it;
for (int i = 1; i <= q; i ++) {
it = se.find(i);
if (it == se.end())
cout << "No" << endl;
else
cout << "Yes" << endl;
}
return 0;
}