AtCoder Beginner Contest 294
A - Filter (abc294 a)
题目大意
给定一个数组,不改变原顺序,输出是偶数的数。
解题思路
模拟即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
while(n--){
int x;
cin >> x;
if (!(x & 1))
cout << x << ' ';
}
cout << '\n';
return 0;
}
B - ASCII Art (abc294 b)
题目大意
给定一个二维数组,将其中的0
变成.
,其余的\(1 \sim 26\)则变成\(A \sim Z\),输出变换后的数组。
解题思路
模拟即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int h, w;
cin >> h >> w;
while(h--){
string s;
for(int i = 0; i < w; ++ i){
int x;
cin >> x;
if (x == 0)
s += '.';
else
s += 'A' + (x - 1);
}
cout << s << '\n';
}
return 0;
}
C - Merge Sequences (abc294 c)
题目大意
给定两个严格升序数组\(A,B\),且两两互不相同。令 \(C\)为 该两个数组合并后并重新升序排序。
输出原在数组\(A,B\) 的每个元素在数组\(C\)的位置。
解题思路
即对每个元素求,有多少个数比它小。
对于\(A\)中的每个元素 \(i\),首先在 \(A\)中有 \(i-1\)个元素比它小。其次对于在 \(B\)数组中,求 出前 \(x\)个都比它小。
当 \(i\)递增时,因为大小关系具有连续性,因此在 \(B\)数组中比它小的数可以在上次状态的基础上继续求。
即双指针法,时间复杂度为 \(O(n+m)\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> a(n), b(m);
for(auto &i : a)
cin >> i;
for(auto &i : b)
cin >> i;
int cur = 0;
for(int i = 0; i < n; ++ i){
while(cur < m && a[i] > b[cur])
++ cur;
cout << (i + 1) + cur << ' ';
}
cout << '\n';
cur = 0;
for(int i = 0; i < m; ++ i){
while(cur < n && b[i] > a[cur])
++ cur;
cout << (i + 1) + cur << ' ';
}
cout << '\n';
return 0;
}
D - Bank (abc294 d)
题目大意
有\(n\)个人编号 \(1 \sim n\),依次发生 \(q\)次事件,分三种:
1
呼叫编号最小的未叫到的号2 x
标号为 \(x\)的人来报道3
呼叫已呼叫但未报道的
标号最小的人
对于每个第三事件,回答叫到标号是多少。
解题思路
第一事件一定是递增的,所以用一个变量维护就好了。
而第三事件,我们只需记录下每个人的报道状态,其叫到的号也是递增的,所以也用一个变量维护就好了。
因为保证数据合法,其实就没别的要考虑的了。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, q;
cin >> n >> q;
vector<int> vis(n);
int cur = 0;
int noww = 0;
while(q--){
int op;
cin >> op;
if (op == 1){
++ cur;
}else if (op == 2){
int id;
cin >> id;
-- id;
vis[id] = 1;
}else{
while(vis[noww] == 1)
++ noww;
cout << noww + 1 << '\n';
}
}
return 0;
}
E - 2xN Grid (abc294 e)
题目大意
给定两个长度均为\(L\)的数组\(a,b\),求下标 \(i\)满足\(a_i = b_i\) 的数量。
每个数组以若干个二元组\((v,l)\)依次表示,即有连续 \(l\)个值为 \(v\),不断延续。
解题思路
因为长度有\(10^{12}\)不能暴力匹配,但考虑到两个数组的切割点(就是二元组数量)只有 \(O(10^5)\),因此我们就以切割点,每块每块考虑就可以了。
感觉非常暴力啊
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL l;
int n, m;
cin >> l >> n >> m;
vector<array<LL, 2>> a(n), b(m);
for(auto &i : a)
cin >> i[0] >> i[1];
for(auto &i : b)
cin >> i[0] >> i[1];
LL ans = 0;
int aa = 0, bb = 0;
while(true){
LL minn = min(a[aa][1], b[bb][1]);
if (a[aa][0] == b[bb][0])
ans += minn;
a[aa][1] -= minn;
b[bb][1] -= minn;
if (a[aa][1] == 0)
++ aa;
if (b[bb][1] == 0)
++ bb;
if (aa == n || bb == m)
break;
}
cout << ans << '\n';
return 0;
}
F - Sugar Water 2 (abc294 f)
题目大意
两排糖水,糖成份\(x\)和水成分 \(y\)。
从两排中各取一个糖水混合,得糖浓度 \(\frac{x_i + x_j}{x_i + x_j + y_i + y_j} \%\)。
问所有方案中,糖浓度第 \(k\)大是多少。
解题思路
考虑一经典问题:两个数组\(a,b\)选各选一个数,求其和是第\(k\)大的数。
其解法是二分,当我们二分这个第 \(k\)大的和是 \(x\)后,我们就看是否有 \(k - 1\)个\(a_i + b_j > x\)。
暴力验证的复杂度是\(O(n^2)\),但可以优化,即对\(a,b\)排序,然后遍历每个 \(a_i\),统计下\(b\)数组中大于\(x - a_i\)的数量。因为\(a_i\)递增,所以 \(x - a_i\)递减,其\(b\)数组中的数量会越来越多,且每次求解继承前一状态继续求即可。因此可以双指针求解。(与\(C\)题做法一致)
即\(b_i > x - a_i\)
因为有单调性的缘故,验证的复杂度可以降为\(O(n)\),总的复杂度是 \(O(n\log n)\)
而对于此题,很明显就是将\(a_i+b_i\)替换成了上述分式。
同样考虑二分值\(mid\),但此时 没有上述的单调性:一个份糖水与另一份糖水混合在一起后其浓度我们找不到某种单调性优化验证的复杂度,做不到 \(O(n)\)(暴力验证是\(O(n^2)\))
但我们还是考虑是否存在某些单调性,我们考虑其比较式子:
分母移项,得
然后我们将\(i,j\)分离,得
这个式子其实就跟上述经典问题转换后的原式子是一样的形式,从这个式子我们就可以窥见另一种单调性:
对于满足原式的\(i,j\)的数量,相当于新数组 \(tmpa[i] = (1 - mid) \times x_i - mid \times y_i, tmpb[j] = (mid - 1) \times x_j + mid \times y_j\),满足\(tmpa[i] > tmpb[j]\)的数量。
而这很显然对\(tmpa,tmpb\)数组排个序后,枚举每个 \(tmpa\),求出小于其的 \(tmpb\)的元素数量,累计就可以了。因为大小具有传递性,因此其 \(tmpb\)的元素数量是递增的,可以从上一个状态继续求解,即双指针法。
因此验证的复杂度就是 \(O(n \log n + m \log m)\)
因此总的时间复杂度就是\(O((n \log n + m\log m) \log 10^9)\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const double eps = 1e-12;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
LL k;
cin >> n >> m >> k;
vector<array<LL, 2>> a(n), b(m);
for(auto &i : a)
cin >> i[0] >> i[1];
for(auto &i : b)
cin >> i[0] >> i[1];
double l = 0, r = 1;
auto check = [&](double x){
vector<double> tmpa(n), tmpb(m);
for(int i = 0; i < n; ++ i){
tmpa[i] = (1 - x) * a[i][0] - x * a[i][1];
}
for(int i = 0; i < m; ++ i){
tmpb[i] = (x - 1) * b[i][0] + x * b[i][1];
}
sort(tmpa.begin(), tmpa.end());
sort(tmpb.begin(), tmpb.end());
LL cnt = 0;
LL cur = 0;
for(int i = 0; i < n; ++ i){
while(cur < m && tmpb[cur] < tmpa[i])
++ cur;
cnt += cur;
}
return cnt <= k;
};
-- k;
while(r - l > eps){
double mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid;
}
cout << fixed << setprecision(11) << r * 100 << '\n';
return 0;
}
G - Distance Queries on a Tree (abc294 g)
题目大意
给定一棵树,边有边权,维护一下两种操作:
1 x w
将第\(x\)条边边权改为 \(w\)2 u v
问从\(u\)到 \(v\)的最短路径长度
解题思路
首先随便选个点作为根。
如果没有修改操作,我们可以维护每个点到根节点的距离\(dis\),记u,v
的lca(最近公共祖先)
是x
,那\(u -> v\)的距离就是 \(dis[u] + dis[v] - 2 dis[x]\)。
而如果有修改操作,每次修改则会影响到一棵子树的 \(dis\)值。从一棵树的 dfs序
来看的话就是修改一个区间。
用线段树维护dfs序
上的\(dis\)数组即可。
区间修改和单点查询。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
class segtree {
#define lson (root << 1)
#define rson (root << 1 | 1)
int n;
vector<LL> max;
vector<LL> lazy;
void build(int root, int l, int r, const vector<int> &v, const vector<LL> &val) {
if (l == r) {
lazy[root] = 0;
max[root] = val[v[l - 1]];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid, v, val);
build(rson, mid + 1, r, v, val);
max[root] = std::max(max[lson], max[rson]);
lazy[root] = 0;
}
inline void pushdown(int root){
if (lazy[root]){
lazy[lson] += lazy[root];
max[lson] += lazy[root];
lazy[rson] += lazy[root];
max[rson] += lazy[root];
lazy[root] = 0;
}
}
void update(int root, int l, int r, int L, int R, LL val){
if (L <= l && r <= R){
lazy[root] += val;
max[root] += val;
return;
}
pushdown(root);
int mid = (l + r) >> 1;
if (L <= mid)
update(lson, l, mid, L, R, val);
if (R > mid)
update(rson, mid + 1, r, L, R, val);
max[root] = std::max(max[lson], max[rson]);
}
LL query(int root, int l, int r, int pos){
if (l == r)
return max[root];
pushdown(root);
int mid = (l + r) >> 1;
if (pos <= mid)
return query(lson, l, mid, pos);
else
return query(rson, mid + 1, r, pos);
}
public:
void build(const vector<int> &v, const vector<LL> &val){
n = v.size();
lazy.resize(4 * (n + 1), 0);
max.resize(4 * (n + 1), 0);
build(1, 1, n, v, val);
}
void update(int L, int R, LL val){
assert(1 <= L && R <= n && L <= R);
update(1, 1, n, L, R, val);
}
LL query(int pos){
return query(1, 1, n, pos);
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<vector<array<LL, 2>>> edge(n);
vector<array<int, 3>> raw(n);
vector<int> deep(n);
vector<LL> dis(n);
vector<array<int, 31>> up(n);
for(int i = 1; i < n; ++ i){
int u, v, w;
cin >> u >> v >> w;
-- u, -- v;
edge[u].push_back({v, w});
edge[v].push_back({u, w});
raw[i] = {u, v, w};
}
vector<int> order;
vector<int> first(n);
vector<int> last(n);
function<void(int, int)> dfs = [&](int u, int fa){
first[u] = order.size();
order.push_back(u);
for(auto &[v, w] : edge[u]){
if (fa == v)
continue;
dis[v] = dis[u] + w;
deep[v] = deep[u] + 1;
up[v][0] = u;
dfs(v, u);
}
last[u] = order.size() - 1;
};
auto get_lca = [&](int u, int v){
if (deep[u] < deep[v])
swap(u, v);
int t = deep[u] - deep[v];
for(int i = 0; i < 31; ++ i){
if (t & (1 << i))
u = up[u][i];
}
for(int i = 30; i >= 0; -- i){
int uu = up[u][i], vv = up[v][i];
if (uu != vv){
u = uu;
v = vv;
}
}
return u == v ? u : up[u][0];
};
deep[0] = 0;
up[0][0] = 0;
dfs(0, 0);
segtree seg;
seg.build(order, dis);
for(int i = 1; i <= 30; ++ i){
for(int j = 0; j < n; ++ j){
up[j][i] = up[up[j][i - 1]][i - 1];
}
}
int q;
cin >> q;
while(q--){
int op;
cin >> op;
if (op == 1){
int id, val;
cin >> id >> val;
if (raw[id][2] == val)
continue;
auto [u, v, w] = raw[id];
if (deep[u] < deep[v])
swap(u, v);
LL cval = val - w;
seg.update(first[u] + 1, last[u] + 1, cval);
raw[id][2] = val;
}else{
int u, v;
cin >> u >> v;
-- u;
-- v;
LL dis1 = seg.query(first[u] + 1);
LL dis2 = seg.query(first[v] + 1);
int lca = get_lca(u, v);
LL dis3 = seg.query(first[lca] + 1);
cout << dis1 + dis2 - 2 * dis3 << '\n';
}
}
return 0;
}
Ex - K-Coloring (abc294 h)
题目大意
给定一张图,要求给点分配值域为\([1,k]\)的点权,问有多少种方案数,使得每条边的两个顶点的点权都不同。
解题思路
<++>
神奇的代码