AtCoder Beginner Contest 292
A - CAPS LOCK (abc292 a)
题目大意
给定一个小写字母串,将其转换成大写字母。
解题思路
调库,或者按照ascii码转换即可。
神奇的代码
#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);
string s;
cin >> s;
for(auto &i : s)
i = toupper(i);
cout << s << endl;
return 0;
}
B - Yellow and Red Card (abc292 b)
题目大意
\(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, q;
cin >> n >> q;
vector<vector<int>> cnt(2, vector<int>(n, 0));
while(q--){
int c, x;
cin >> c >> x;
c --;
x --;
if (c == 2){
if (cnt[0][x] < 2 && cnt[1][x] < 1)
cout << "No" << '\n';
else
cout << "Yes" << '\n';
}else
cnt[c][x] ++;
}
return 0;
}
C - Four Variables (abc292 c)
题目大意
给定\(n\),问有多少正整数组\((A,B,C,D)\),满足 \(AB + CD = n\)
解题思路
给定\(X\),预处理\(AB=X\) 的方案数\(cnt[X]\)。
然后枚举\(AB\)的乘积 \(X\),则 \(CD\)的乘积为 \(n-X\),其两个方案数相乘\(cnt[X] \times cnt[n - X]\)。然后累加即是答案。
时间复杂度为 \(O(n\log n)\)
神奇的代码
#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;
vector<int> cnt(n + 1);
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= n; ++ j){
if (1ll * i * j > n)
break;
cnt[i * j] ++;
}
LL ans = 0;
for(int i = 1; i < n; ++ i)
ans += 1ll * cnt[i] * cnt[n - i];
cout << ans << '\n';
return 0;
}
D - Unicyclic Components (abc292 d)
题目大意
给定一张无向图,问每个连通块是否满足:其边数
与点数
相等。
解题思路
用并查集
维护连通性,同时维护连通块的边数
和点数
,最后一一判断即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
class dsu {
public:
vector<int> p;
vector<int> psz;
vector<int> esz;
int n;
dsu(int _n) : n(_n) {
p.resize(n);
psz.resize(n);
esz.resize(n);
iota(p.begin(), p.end(), 0);
fill(psz.begin(), psz.end(), 1);
fill(esz.begin(), esz.end(), 0);
}
inline int get(int x) {
return (x == p[x] ? x : (p[x] = get(p[x])));
}
inline bool unite(int x, int y) {
x = get(x);
y = get(y);
esz[y] ++;
if (x != y) {
p[x] = y;
psz[y] += psz[x];
esz[y] += esz[x];
return true;
}
return false;
}
inline bool check(){
for(int i = 0; i < p.size(); ++ i){
if (get(i) == i && psz[i] != esz[i]){
return false;
}
}
return true;
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
dsu d(n);
for(int i = 0; i < m; ++ i){
int u, v;
cin >> u >> v;
-- u, -- v;
d.unite(u, v);
}
if (d.check())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}
E - Transitivity (abc292 e)
题目大意
给定一张有向图。如果对于三个点\((a,b,c)\), \(a->b\), \(b->c\),则必须有边\(a->c\) 。
问最少添加多少条边,使得对于任意三个点,都满足以上性质。
解题思路
考虑一条链,从左连到右,容易发现左边的点与右边的每个点都要连一条边。
即从一个点出发,它能到达的所有点,在最终的图都要与其连边。
因此从每个点\(BFS\),求得其能到达的所有点数。对所有点累加即是最终图的边数,减去已有的边数,则是需要添加的最小边数。
神奇的代码
#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<vector<int>> edge(n);
for(int i = 0; i < m; ++ i){
int u, v;
cin >> u >> v;
-- u, -- v;
edge[u].push_back(v);
}
int ans = 0;
auto bfs = [&](int st){
queue<int> team;
team.push(st);
int cnt = 0;
vector<int> visit(n, 0);
visit[st] = 1;
while(!team.empty()){
auto u = team.front();
team.pop();
for(auto &v : edge[u]){
if (!visit[v]){
++ cnt;
team.push(v);
visit[v] = 1;
}
}
}
return cnt;
};
for(int i = 0; i < n; ++ i)
ans += bfs(i);
ans -= m;
cout << ans << '\n';
return 0;
}
F - Regular Triangle Inside a Rectangle (abc292 f)
题目大意
给定一个矩阵,问其内接的最大正三角形的边长是多少。
解题思路
从样例的图我们可以进行猜测:
- 三角形的一个顶点在矩形的顶点上
- 当矩形的长不够长时,三角形的另外两个点都在矩形的边上。
设\(15^\circ\)的角为 \(\theta\),矩形长\(a\),宽 \(b\)(\(a > b\)),三角形边长 \(x\),根据正三角形边相等和高中数学知识可得
用和角公式将\(\cos(30^{\circ} - \theta)\)拆开,然后解方程得到
因为这里\(0 \leq \theta \leq 30^\circ\),所以 \(\tan \theta\)应该大于 \(0\)。因此这里就有个边界条件 \(\frac{2b}{a} > \sqrt{3}\)
一旦不满足,说明长 \(a\)太大了,此时三角形最大的情况,就是其高和矩形的宽相等,即三角形的边长就是 \(x = \frac{2b}{\sqrt{3}}\)
否则,算得\(\cos \theta = \sqrt{ \frac{1}{1 + tan^2 \theta}}\),\(x = \frac{a}{cos \theta}\)
神奇的代码
#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 a, b;
cin >> a >> b;
if (a < b)
swap(a, b);
long double sq3 = sqrt(3);
if (2.0 * b / a < sq3){
double x = b / sq3 * 2;
cout << fixed << setprecision(15) << x << '\n';
}else {
long double tan = 2.0 * b / a - sq3;
long double cos = sqrt(1 / (1 + tan * tan));
long double x = a / cos;
cout << fixed << setprecision(15) << x << '\n';
}
return 0;
}
G - Count Strictly Increasing Sequences (abc292 g)
题目大意
给定\(n\)个长度为 \(m\)的包含数字和 ?
的字符串。
将这些 ?
替换数字。问有多少种替换方案,满足\(s_0 < s_1 < s_2 < ... < s_{n-1}\)
解题思路
应该是个数位DP竟然是区间\(DP\)。
首先数的大小可以转换成字典序大小的比较。
状态切分点
来源于字典序大小的定义
的递归性: \(s < t\),当且仅当 \(s[0] < t[0]\),或者 \(s[0] = t[0]\) 且 \(s[1..m] < t[1..m]\),而 \(s[1..m] < t[1..m]\)可以看成原问题\(s<t\)(或者可以看成 \(s[0...m] < t[0...m]\))的一个子问题。
因此,我们考虑要确保\(s_0 < s_1 < s_2 < ... < s_{n-1}\),那首先会有前\(k\)个串,其第一个数字是相同的,假设为\(f\),即\(s_0[0] = s_1[0] = s_2[0] = ... = s_{k-1}[0] = f\)
此时问题就转换成:前\(k\)个串的后 \(m-1\)个数字的满足题目条件的方案数,与,后面串的 \(m\)个数字的满足题目条件,且第一个数字要大于\(f\)的方案数,的乘积。
即设\(dp[l][r][k][f]\)表示区间 \([l,r]\)的字符串,考虑\([k..m-1]\)的位置, 且第\(k\)个数字是大于等于 \(f\)的,满足题目条件的方案数。
那么原问题是 \(dp[0][n - 1][0][0]\),当前\(k\)个串的第一个数字都是 \(l\)时(这意味着后面串的第一个数字要大于\(l\)),原问题就切分成两部分的乘积: \(dp[0][k - 1][1][0] \times dp[k][n - 1][0][l + 1]\)
根据字典序大小的递归定义,第一项就是\(s[0] = t[0]\) 且 \(s[1..m] < t[1..m]\)的情况,第二项就是\(s[0] < t[0]\)的情况,都是满足\(s < t\)的条件。
由此枚举\(k\)转移即可,写成 \(dfs\)就不用脑子了,注意下边界条件。
状态复杂度是\(O(10n^2m)\),转移是 \(O(n)\),总复杂度是 \(O(10n^3m)\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<string> s(n);
for(auto &i : s)
cin >> i;
vector<vector<vector<vector<int>>>> dp(n, vector<vector<vector<int>>>(n, vector<vector<int>>(m, vector<int>(10, -1))));
function<int(int, int, int, int)> dfs = [&](int l, int r, int k, int f){
if (k == m)
return int(l == r);
if (l > r)
return 1;
if (f >= 10)
return 0;
if (dp[l][r][k][f] != -1)
return dp[l][r][k][f];
int val = dfs(l, r, k, f + 1);
for(int i = l; i <= r; ++ i){
if (s[i][k] != '?' && s[i][k] != '0' + f)
break;
val = val + 1ll * dfs(l, i, k + 1, 0) * dfs(i + 1, r, k, f + 1) % mo;
val %= mo;
}
return dp[l][r][k][f] = val;
};
int ans = dfs(0, n - 1, 0, 0);
cout << ans << '\n';
return 0;
}
Ex - Rating Estimator (abc292 h)
题目大意
给定\(n\)场比赛的表现分\(a_i\),经过第 \(k\) 场比赛后,\(rating\)将变成 \(\frac{\sum_{i=1}^{k} a_i}{k}\) 。但当\(rating\)超过 \(B\)后便不再涨了。
处理一下 \(q\)次操作,输出每次操作完后,经过这 \(n\)场比赛最后的 \(rating\)值。
每次操作给定 \(c, x\),将第 \(c\)场比赛的 表现分 \(a_c\)更改为 \(x\)。
操作是持久化的。
解题思路
先考虑不修改的,这里用到一个转换技巧跟abc236 E一样。
我们要找\(\frac{\sum_{i=1}^{k} a_i}{k} > B\),即\(\sum_{i=1}^{k} a_i - kB > 0\),即\(\sum_{i=1}^{k} (a_i - B) > 0\)
即对于新数组\(b_i = a_i - B\),原问题找最小的 \(k\)使得\(a\)满足上述条件,就转换成找最小的\(k\)使得 \(b\)满足前缀和大于 \(0\)。
维护前缀和的最大值,然后二分查找。
考虑修改的话,用线段树维护这个前缀和的最大值,然后在线段树上二分就可以了。复杂度不变,都是\(O(q\log n)\)
神奇的代码
#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> sum;
vector<LL> max;
void build(int root, int l, int r, const vector<LL> &v) {
if (l == r) {
sum[root] = v[l - 1];
max[root] = v[l - 1];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid, v);
build(rson, mid + 1, r, v);
sum[root] = sum[lson] + sum[rson];
max[root] = std::max(max[lson], sum[lson] + max[rson]);
}
void update(int root, int l, int r, int pos, LL val){
if (l == r){
sum[root] = val;
max[root] = val;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
update(lson, l, mid, pos, val);
else
update(rson, mid + 1, r, pos, val);
sum[root] = sum[lson] + sum[rson];
max[root] = std::max(max[lson], sum[lson] + max[rson]);
}
pair<int, LL> query(int root, int l, int r, LL presum){
if (l == r)
return {l, presum + max[root]};
int mid = (l + r) >> 1;
if (presum + max[lson] >= 0)
return query(lson, l, mid, presum);
else
return query(rson, mid + 1, r, presum + sum[lson]);
}
public:
void build(const vector<LL> &v){
n = v.size();
sum.resize(4 * (n + 1), 0);
max.resize(4 * (n + 1), 0);
build(1, 1, n, v);
}
void update(int pos, LL val){
assert(1 <= pos && pos <= n);
update(1, 1, n, pos, val);
}
pair<int, LL> query(){
return query(1, 1, n, 0);
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, q;
LL b;
cin >> n >> b >> q;
vector<LL> a(n);
for(auto &i : a){
cin >> i;
i -= b;
}
segtree seg;
seg.build(a);
while(q--){
int c;
LL x;
cin >> c >> x;
seg.update(c, x - b);
auto [pos, sum] = seg.query();
double ans = b + 1.0 * sum / pos;
cout << fixed << setprecision(15) << ans << '\n';
}
return 0;
}