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)

题目大意

给定一个矩阵,问其内接的最大正三角形的边长是多少。

解题思路

从样例的图我们可以进行猜测:

  • 三角形的一个顶点在矩形的顶点上
  • 当矩形的长不够长时,三角形的另外两个点都在矩形的边上。

example

\(15^\circ\)的角为 \(\theta\),矩形长\(a\),宽 \(b\)\(a > b\)),三角形边长 \(x\),根据正三角形边相等和高中数学知识可得

\[x = \frac{a}{\cos \theta} = \frac{b}{\cos(30^{\circ} - \theta)} \]

用和角公式将\(\cos(30^{\circ} - \theta)\)拆开,然后解方程得到

\[\tan \theta = \frac{2b}{a} - \sqrt{3} \]

因为这里\(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;
}



posted @ 2023-03-04 22:36  ~Lanly~  阅读(478)  评论(1编辑  收藏  举报