Atcoder Beginner Contest 342 全题解

A - Yay!

题意

  • 给定字符串 s

  • 已知该字符串中只有一个字符与其他字符不同。

  • 求这个字符。

思想

开一个数组 cnti 来记录 s 中每个字符出现的次数,一个数组 firsti 来记录 s 中每个字符第一次出现的下标。

选择 cnti=1i 输出 firsti 即可。

代码

#include <bits/stdc++.h>
using namespace std;
int cnt[26], f[26];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    string s;
    cin >> s;
    int n = s.size();
    for (int i = 0; i < n; i++) {
        cnt[s[i] - 'a']++;
        if (cnt[s[i] - 'a'] == 1)
            f[s[i] - 'a'] = i;
    }
    for (int i = 0; i < 26; i++) {
        if (cnt[i] == 1) {
            cout << f[i] + 1 << endl;
        }
    }

    return 0;
}

B - Which is ahead?

题意

  • n 个人站成一排,从前往后第 i 个人是 Pi

  • 处理 q 次询问。

  • 每次询问 aibi 哪个更靠前,输出人的编号。

思想

每次询问从前向后扫描,先扫描到 ai 就输出 ai,否则输出 bi

代码

#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 105;
int n, q;
int p[MAXN];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> p[i];
    cin >> q;
    for (int i = 1; i <= q; i++) {
        int a, b;
        cin >> a >> b;
        int x, y;
        bool flag = false;
        for (int j = 1; j <= n; j++) {
            if (p[j] == a) {
                if (!flag) {
                    cout << a << endl;
                }
                flag = true;
            }
            if (p[j] == b) {
                if (!flag) {
                    cout << b << endl;
                }
                flag = true;
            }
        }
    }

    return 0;
}

C - Many Replacement

题意

  • 给你一个长度为 n 的字符串 s

  • q 次修改。

  • 每次修改将 s 中的所有字符 ci 更改为 di

  • 输出最后的字符串。

思想

如果直接按照题目要求模拟的话,时间复杂度为 O(n2),在 n2×105 的情况下会 TLE。

考虑记录每个字符 c 最终会变字符 toc

动态维护 toc

每次更改将所有会更改为 ci 的字符的 toa 改为 di

即对于所有 toa=ciatoa=di

最后将 s 的每个字符变为相应的 to 就行了。

时间复杂度 O(Cn),其中 C 为字符集大小。

代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 2e5 + 5;
int n, q;
string s;
int to[26], ans[26];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    cin >> s;
    cin >> q;
    for (int i = 0; i < 26; i++)
        to[i] = i;
    while (q--) {
        string c1, d1;
        cin >> c1 >> d1;
        int c = c1[0] - 'a';
        int d = d1[0] - 'a';

        for (int i = 0; i < 26; i++) {
            if (to[i] == c) {
                to[i] = d;
            }
        }
    }

    for (int i = 0; i < n; i++) {
        s[i] = to[s[i] - 'a'] + 'a';
    }
    cout << s << endl;

    return 0;
}

D - Square Pair

题意

  • 给你一长度为 n 的序列 {ai}

  • ai×aj 为平方数的无序数对 (i,j) 的个数。

思想

如果直接按照题意模拟复杂度高达 O(n2a),会 TLE。

根据算数基本定理,我们将 ai 分解。

a=p1α1p2α2p2α2pkαk

考虑什么时候 ai×aj 为平方数。

t 为平方数,则:

α10mod2

α20mod2

αk0mod2

此时要让他为平方数则要求:

α1α1mod2

α2α2mod2

αkαkmod2

我们可以给 ai 记录下序列 {xi}αj1mod2pj

这样只需要 aiajx 相同相乘即为平方数。

但是这里有个特例:0 乘任何数都等于 0,为平方数。

所以我们先把 0 处理掉,设 0 的个数为 cnt

0 乘非 0 的个数为 cnt×(ncnt)

00 的个数为 cnt×(cnt1)2

所以答案的贡献先算上 cnt×(ncnt)+cnt×(cnt1)2

我们接着把每一个 ai 都计算出他们的 x,放到 map 里统计同一个 x 的有多少个。

若这一种的数量为 j 则对答案的贡献为 j×(j1)2

按照此方法计算即可。

此处 xvector 记录,因为 vector 可以方便的用 map 统计。

代码

#include <bits/stdc++.h>
using namespace std;

#define int long long

constexpr int MAXN = 2e5 + 5;
int n;
int a[MAXN];

map<vector<int>, int> mp;

void build(int x) {
    vector<int> ans;
    map<int, int> tmp;
    for (int i = 2; i * i <= x; i++) {
        if (x % i == 0) {
            while (x % i == 0) {
                x /= i;
                tmp[i]++;
            }
        }
    }
    if (x > 1) {
        tmp[x]++;
    }

    for (auto [i, j] : tmp) {
        if ((j & 1) == 0)
            continue;
        ans.push_back(i);
    }
    mp[ans]++;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    long long ans = 0;
    long long cnt = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        if (a[i] == 0)
            cnt++;
        else build(a[i]);
    }
    ans += cnt * (n - cnt) + cnt * (cnt - 1) / 2;
    for (auto [i, j] : mp) {
        ans += 1ll * j * (j - 1) / 2;
    }
    cout << ans << endl;

    return 0;
}

E - Last Train

题意

  • n 个站台,m 种列车。

  • i 种列车有 ki 次单程车。

  • 第一次单程车发车于 li

  • 每次单程车都需要花 ci 单位的时间来到达终点。

  • 每相邻两次单程车发车时间相距 di 单位时间。

  • 出发站为 ai

  • 结束站为 bi

  • 求对于每个点 i[1,n1] 求出 f(i) 即从 i 号站出发要能到达 n 号站的最晚时间。

思想

由题面可知这是一道图论题。

感性理解一下这是一个类似最短路的问题。

题中的列车网络是一个有向图,但不一定无环。

这样拓扑排序就被 pass 了。

如果是 BFS 它又有边权。

观察一下,不可能其他都一样只是多坐了一班车还更优。

所以我们选用堆优化 Dijkstra

注意要反向建图,本题是单终最短路。

我们可以按照最优顺序来排序 Dijkstra 的松弛顺序,在本题中为按照 f(i) 从大到小访问。

如何松弛呢?

考虑计算要在 f(u) 之前从 v 到达 u 的最晚距离。

f(u)l+td+c

f(v)l+td

其中 t 为我们坐哪一次车。

我们选择最大的 t

t=f(u)lcd

我们注意到 0t<k

如果错过了这班车就不能松弛,但是如果太早了可以等。

错过这班车即 l+c>f(u)

太早了等即

t=min{f(u)lcd, k1}

我们把 t 带入 f(v) 的式子。

f(v)l+min{f(u)lcd, k1}d

我们只要发现 f(v)< 这个值,就可以来更新它。

代码

#include <bits/stdc++.h>
using namespace std;

#define int long long
constexpr int MAXN = 2e5 + 5;
int n, m;

struct Node {
    int l, d, k, c, to;
};
vector<Node> G[MAXN];

int f[MAXN];

void Dijkstra() {
    memset(f, 0xc0, sizeof f);
    f[n] = 0x3f3f3f3f3f3f3f3f;
    set<pair<int, int>, greater<>> st;
    st.insert(make_pair(0x3f3f3f3f3f3f3f3f, n));
    while (st.size()) {
        int u = st.begin()->second;
        st.erase(st.begin());
        for (auto [l, d, k, c, v] : G[u]) {
            if (l + c > f[u])
                continue;
            if (f[v] < l + min((f[u] - c - l) / d, k - 1) * d) {
                st.erase(make_pair(f[v], v));
                f[v] = l + min((f[u] - c - l) / d, k - 1) * d;
                st.insert(make_pair(f[v], v));
            }
        }
    }
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int l, d, k, c, a, b;
        cin >> l >> d >> k >> c >> a >> b;
        G[b].push_back({ l, d, k, c, a });
    }

    Dijkstra();
    for (int i = 1; i < n; i++) {
        if (f[i] == 0xc0c0c0c0c0c0c0c0)
            cout << "Unreachable" << endl;
        else
            cout << f[i] << endl;
    }

    return 0;
}

F - Black Jack

题意

  • 你在跟一个叫庄稼的人玩游戏。

  • 有一个有 d 面的色子,它会等概率地摇出 1d 的整数。

  • 你先摇,每一次都把摇出来的结果加到 x 上去,你可以选择摇几次。

  • 庄稼后摇,只要它摇出来的点数 <l,就会继续摇,同样地,它会把每次摇出来的数加到 y 上。

  • 如果 x>nxyn 你就输了,否则你就赢了。

  • 求你获胜的概率最大是多少。

思想

因为庄稼的策略是固定的,所以我们计算 g(i) 即庄稼最后摇到了 x 的点数的概率

注意到每次 i<l 再摇时会把概率平均分到 i+1i+d

我们就从小往大向后贡献。

但是这样会 TLE。

区间加和单点查询。

我们用差分树状数组来维护它。

我们为了计算小于等于的贡献就直接前缀和一下得到 g(i)

我们计算出 g(i) 了之后开始计算答案。

我们先计算在 i 处我们停止摇色子的获胜概率。

i>l 则概率为 g(x1)+1g(n)

i<l 则概率为 1g(n)

我们令这个函数为 calc(i)

然后我们令 f(i) 为我们从 x=i 开始摇色子最多的获胜概率。

如果继续摇答案就是 j=1df(i+j)d

停下答案就是 calc(i)

所以:

fi=max{calc(i),j=1df(i+j)d}

我们从后往前扫描,用 sum 来维护 j=1df(i+j)

代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 4e5 + 5;

int lowbit(int x) {
    return x & -x;
}

struct Fenwick {
    double c[MAXN];

    void upd(int l, int r, double x) {
        for (int i = l + 1; i < MAXN; i += lowbit(i))
            c[i] += x;
        for (int i = r + 2; i < MAXN; i += lowbit(i))
            c[i] -= x;
    }

    double ask(int x) {
        double ans = 0;
        for (int i = x + 1; i > 0; i -= lowbit(i))
            ans += c[i];
        return ans;
    }
} fenwick;

int n, l, d;
double f[MAXN], g[MAXN];

double calc(int x) {
    if (x > n)
        return 0;
    double ans = 1 - g[n];
    if (x > l)
        ans += g[x - 1];
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> l >> d;
    fenwick.upd(0, 0, 1);
    for (int i = 0; i <= 4e5; i++) {
        g[i] = fenwick.ask(i);
        if (i < l) {
            fenwick.upd(i + 1, i + d, g[i] / d);
            g[i] = 0;
        }
    }
    for (int i = 1; i <= 4e5; i++)
        g[i] += g[i - 1];

    double sum = 0;
    for (int i = 4e5; i >= 0; i--) {
        if (i > n)
            f[i] = 0;
        else
            f[i] = max(sum / d, calc(i));
        sum += f[i];
        if (i + d <= 4e5)
            sum -= f[i + d];
    }
    cout << fixed << setprecision(15) << f[0] << endl;

    return 0;
}

G - Retroactive Range Chmax

题意

  • 给定一个长度为 n 的序列 a

  • q 次操作。

  • 1 操作:对所有 i[l,r] 进行 ai=max(ai,x)

  • 2 操作:取消第 i 次操作,保证第 i 为 1 操作,即新的状态为从前往后做除了 i 操作外的所有操作得到的状态。

  • 3 操作:询问 ai

思想

如果没有 2 操作就是吉老师线段树裸题。

但是加上了 2 操作就变得十分棘手。

实际上我们并不要求复杂度一定是 O(nlogn)O(nlog2n) 也可以。

我们仍旧使用线段树,但是每个节点维护一个 multiset。

维护当前节点取过 max 的数值和原本的值。

这样撤销只要在 multiset 里删除相应 x 即可。

询问就返回 multiset 里最大的一个。

取 max 就在 multiset 里加入 x

代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 8e5 + 5;

struct SegTree {
    multiset<int, greater<>> body[MAXN];
    int ls[MAXN], rs[MAXN];

    void build(int k, int l, int r) {
        ls[k] = l;
        rs[k] = r;
        body[k].clear();
        if (l == r)
            return;
        int lc = k * 2;
        int rc = k * 2 + 1;
        int mid = l + r >> 1;
        build(lc, l, mid);
        build(rc, mid + 1, r);
    }

    void update(int k, int l, int r, int x) {
        if (l <= ls[k] && rs[k] <= r) {
            body[k].insert(x);
            return;
        }
        int mid = ls[k] + rs[k] >> 1;
        int lc = k * 2;
        int rc = k * 2 + 1;
        if (r <= mid)
            update(lc, l, r, x);
        else if (l > mid)
            update(rc, l, r, x);
        else {
            update(lc, l, mid, x);
            update(rc, mid + 1, r, x);
        }
    }

    void cancel(int k, int l, int r, int x) {
        if (l <= ls[k] && rs[k] <= r) {
            body[k].erase(body[k].find(x));
            return;
        }
        int mid = ls[k] + rs[k] >> 1;
        int lc = k * 2;
        int rc = k * 2 + 1;
        if (r <= mid)
            cancel(lc, l, r, x);
        else if (l > mid)
            cancel(rc, l, r, x);
        else {
            cancel(lc, l, mid, x);
            cancel(rc, mid + 1, r, x);
        }
    }

    int query(int k, int x) {
        if (ls[k] == rs[k])
            return *body[k].begin();
        int ans = 0;
        if (body[k].size())
            ans = *body[k].begin();
        int mid = ls[k] + rs[k] >> 1;
        int lc = k * 2;
        int rc = k * 2 + 1;
        if (x <= mid)
            ans = max(ans, query(lc, x));
        else
            ans = max(ans, query(rc, x));
        return ans;
    }
} segtree;

struct Query {
    int l, r, x;
} a[MAXN];

int n, q;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    segtree.build(1, 1, n);
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        segtree.update(1, i, i, x);
    }

    cin >> q;
    for (int i = 1; i <= q; i++) {
        int opt, x;
        cin >> opt;
        if (opt == 1) {
            cin >> a[i].l >> a[i].r >> a[i].x;
            segtree.update(1, a[i].l, a[i].r, a[i].x);
        } else if (opt == 2) {
            cin >> x;
            segtree.cancel(1, a[x].l, a[x].r, a[x].x);
        } else {
            cin >> x;
            cout << segtree.query(1, x) << endl;
        }
    }

    return 0;
}
posted @   LightningCreeper  阅读(329)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示