2023 年上海市大学生程序设计竞赛 - 五月赛

本次过题目c++标准为c++17


A. 选择

题目

输入文件: standard input
输出文件: standard output
时间限制: 1 second
内存限制: 256 megabytes

选择正确的道路出发吧。
—— 来自虚空的声音
给定一个初始序列 {1, 2, . . . , n},你需要进行若干次操作,将整个序列所有数变成零。每个操作由三步
组成:

  1. 选择一个下标集合 S = {i1, i2, . . . , ik} ⊆ {1, 2, . . . , n};
  2. 选择一个非负整数 x;
  3. 对每个选中的数减去 x:∀i ∈ S, ai ← ai − x。
    请计算在最优策略下需要的操作次数。

输入

输入一行一个正整数 n (1 ≤ n ≤ 10^6)。

输出

输出一行一个正整数,表示最少需要的操作次数。
在这里插入图片描述

注释

对于第三组样例,一种最优方案的两次操作如下:
1. S = {1, 3}, x = 1 : a = {0, 2, 2};
2. S = {2, 3}, x = 2 : a = {0, 0, 0}.
可以证明,没有次数更少的方案。

题解

#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;
    int ans = 1;
    while (n /= 2)
    {
        ans ++;
    }
    cout << ans;
    return 0;
}

B. 锐评

题目

输入文件: standard input
输出文件: standard output
时间限制: 1 second
内存限制: 256 megabytes

如果人们在网上看到一家店的好评率太低,或者读到类似「厨子偷吃」的差评,可能就不会去吃这家饭店了。
不过,如果你是火锅店老板,你也许会想尽办法让自己好评如潮。以下是两个可能的操作:

  1. 花 1 块钱,买水军增加一条好评。
  2. 花 x 块钱,用类似「侵犯名誉权」的理由举报掉一条差评。如果没有差评,你不能这么做。
    假设你有 m 块钱,现在网上有 a 条好评,b 条差评。好评率被定义为 a / (a + b)
    在预算范围内,你会想办法最大化好评率。请计算你最终能的得到的最高好评率。
    由于你的动作真的很快,所以我们假定除了你的操作以外,评价数量不会发生变化。

输入

输入第一行一个整数 T (1 ≤ T ≤ 10^5),表示数据组数。
对于每组数据,输入一行四个整数 m, x, a, b (1 ≤ m, x, a, b ≤ 10^9)。

输出

对于每组数据,输出一行一个小数,表示答案。
如果你的答案在相对误差或者绝对误差 10^−6 以内,将会被认为是正确的。
在这里插入图片描述

题解

#include <iostream>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        long long m, x, a, b;
        cin >> m >> x >> a >> b;
        long double t1 = (long double)(a + m) / (long double)(a + b + m);
        long double t2 = (long double)(a + m % x) / (long double)(max(0LL, b - m / x) + a + m % x);
        printf("%0.9LF\n", max(t1, t2));
    }
    return 0;
}

C. 饮茶

题目

输入文件: standard input
输出文件: standard output
时间限制: 1 second
内存限制: 256 megabytes

活是干不完的,不如饮茶放工。
话虽然这么说,但是放工太久容易导致挂科。接下来 n 天,你每天都有一个作业在晚上 23:59 截止,做这个作业需要恰好 ti 小时。如果认真学习,那么你可以在第 i 天干 ai 小时的活;如果你决定开摆,和同学去饮茶、逛街、吃火锅,那么只能干 bi 小时的活。
为了避免老师捞不动,你决定还是把每个作业按时提交。你可以提前做作业:你可以在每个作业截止前的任意一个工作时间段做任意久该作业。但是,每个作业在截止前必须做完。
劳逸结合很重要!所以你想知道你最多有几天能出去玩。

输入

输入第一行一个正整数 n (1 ≤ n ≤ 10^5)。
接下来 n 行,每行三个整数 ti, ai, bi (0 ≤ ti ≤ 1000, 0 ≤ ai
, bi ≤ 24, ai ≥ bi)。

输出

输入一行一个整数,表示:
• 如果你再怎么努力都已经来不及了,输出 −1。
• 否则输出你最多有几天能出去玩。
在这里插入图片描述

注释

样例 1 解释:人有多大胆,地有多大产。
样例 2 解释:完蛋咯,建议直接开摆。

题解

#include <iostream>
#include <queue>

using namespace std;

const int N = 1e5 + 5;
int t[N], a[N], b[N];
priority_queue<int> q;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, totalt = 0, now = 0, ans = 0;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> t[i] >> a[i] >> b[i];
        q.push(a[i] - b[i]);
        ans++;
        totalt += t[i] - b[i];
        while (!q.empty() && now < totalt) {
            now += q.top();
            q.pop();
            ans--;
        }
        if (now < totalt) {
            cout << -1 << '\n';
            return 0;
        }
    }
    cout << ans << '\n';
    return 0;
}

D. 脚本

题目

输入文件: standard input
输出文件: standard output
时间限制: 3 seconds
内存限制: 1024 megabytes

你最后还是决定饮茶放工,出去和队友吃火锅。不过在此之前火锅店老板已经对评论区进行了一次公关,你已经看不到多少中肯的评价了。同时,你也在评论区发现了一些端倪:因为老板请了水军,评论区的评论呈现出有一些有趣的特点。例如每一条评论的开头都离不开某几句话,因此你用一个小写字母(‘a’ - ‘z’) 来表示一种评论。
你还发现评论都是用比较拙劣的自动化工具刷的,因为不仅只有几种评论,依次查看评论甚至发现了循环节。根据你多年的脚本经验,这个脚本里面有一个长度为 p 的评论列表,这个脚本会按顺序循环往复地发布这 p 条评论。如果我们用字符串 s = s1s2 . . . s|s| 表示一系列连续的评论,那么我们说 p 是 s 的
循环节,当且仅当:
• p ≤ |s|;
• ∀1 ≤ i ≤ |s| − p, si = si+p.
你现在爬取了评论区所有的 n 条评论,你很好奇老板刷评论的脚本里面的评论列表长什么样。不过,在老板操作以前可能已经存在一些评论了,你不知道老板是从哪一条评论开始用脚本刷评论的,因此你想知道每一个后缀的周期性。具体而言,对于 i = 1 . . . n,假设最后的 i 条评论是用脚本刷的,你需要求
出脚本中所有可能循环节的大小(也即评论列表长度)之和ai:
在这里插入图片描述

输入

输入包含多组数据。第一行包含一个整数 T,代表数据组数。
每组数据的第一行包含一个正整数 n (1 ≤ n ≤ 106),代表评论区评论的数量;第二行包含一个仅包含小写字母的字符串 s。
保证所有数据的 n 总和不超过 106。

输出

对每组数据,在一行内输出 n 个用空格分隔的整数 a1, a2, . . . , an。
在这里插入图片描述

注释

对于第一组数据而言:
• 当 i = 1 时,评论列表只有可能为 a。
• 当 i = 2 时,评论列表只有可能为 ba。
• 当 i = 3 时,评论列表可能为 aba 或者 ab。
• 当 i = 4 时,评论列表可能为 baba 或者 ba。
• 当 i = 5 时,评论列表可能为 ababa, abab 或者 ab。

题解

#include <iostream>
#include <algorithm>

using namespace std;

#define int long long

const int N = 2e6 + 5;

int n, ans[N], sum[N], nex[N];
char t[N];

void KMP(const char p[], int m) {
    for (int i = 2, j = 0; i <= m; i++) {
        while (j && p[i] != p[j + 1]) j = nex[j];
        if (p[i] == p[j + 1]) j++;
        nex[i] = j;
    }
}

signed main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> (t + 1);
        reverse(t + 1, t + 1 + n);
        KMP(t, n);
        for (int i = 1; i <= n; i++) {
            int j = i - nex[i];
            sum[i] = sum[nex[i]] + 1;
            ans[i] = ans[nex[i]] + j * sum[i];
            cout << ans[i] << " \n"[i == n];
        }
    }
    return 0;
}

E2. 火锅(困难)

题目

输入文件: standard input
输出文件: standard output
时间限制: 2 seconds
内存限制: 512 megabytes

请注意,本题与(简单)的差别仅在 m 的范围上,其余内容一致。

你和你的队友换了一家评论区未被公关且好评如潮的火锅店吃火锅。
在涮菜的时候,你发现由于下锅时间不一致,而捞出锅时你不一定能捞到你下的菜(也有可能被你急急急的队友给捞走了),你既有可能吃到夹生的鸭肠,也有可能吃到煮久到嚼不动的鸭肠。
我们现在假定,m 种菜品中,菜品 i 需要花 li 秒煮熟,而如果 ri 秒后还没有捞出锅就煮老了;只有在下锅后 li 秒到 ri 秒内捞出的才是恰到好处的。
为了检查你们的涮菜操作是否是合理的,你记录下了完整的操作序列:
• add ti
: 在第 ti 秒下锅了一份菜,这份菜是从 m 种中均匀随机地选取的。
• pop ti
: 在第 ti 秒从锅中均匀随机地捞起了一份菜。进行该项操作时,锅中一定还有菜品。
你想知道,期望下你能吃到多少份恰到好处的菜。

输入

输入第一行包含两个正整数 m (1 ≤ m ≤ 202 305), n (1 ≤ n ≤ 202 305)。
接下来 m 行,每行两个整数 li, ri(1 ≤ li ≤ ri ≤ 202 305),表示一种菜品的好区间。
接下来 n 行,每行为以下两种中的一种:
• add ti
• pop ti
其中,1 ≤ ti ≤ 202 305,且 ti 互不相同,并且随着 i 单调增。

输出

输出一行一个整数,表示期望下你能吃到恰到好处的菜的数量对 998 244 353 取模的结果。
对答案取模的定义如下:可以证明,答案一定能表示成有理数 p/q,其中 p, q 为一对互素的整数(特别地,p 等于 0 时 q 为 1)。此时,你需要输出 p × q^−1 mod 998 244 353,可以证明这个答案唯一。
在这里插入图片描述

题解(参考大佬的答案)

#include <bits/stdc++.h>

#define int long long
#define fp(i, a, b) for (int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define fd(i, a, b) for (int i = (a), i##_ = (b) - 1; i > i##_; --i)
using namespace std;
const int maxn = 3e5 + 5, P = 998244353;
using ll = int64_t;

#define ADD(a, b) (((a) += (b)) >= P ? (a) -=P : 0) // (a += b) %= P
#define SUB(a, b) (((a) -= (b)) < 0 ? (a) += P: 0)  // ((a -= b) += P) %= P
#define MUL(a, b) ((ll) (a) * (b) % P)
int POW(ll a, int b = P - 2, ll x = 1) {
    for (; b; b >>= 1, a = a * a % P) if (b & 1) x = x * a % P;
    return x;
}
namespace NTT {
    const int g = 3;

    vector<int> Omega(int L) {
        int wn = POW(g, P / L);
        vector<int> w(L);
        w[L >> 1] = 1;
        fp(i, L / 2 + 1, L - 1) w[i] = MUL(w[i - 1], wn);
        fd(i, L / 2 - 1, 1) w[i] = w[i << 1];
        return w;
    }

    auto W = Omega(1 << 21); // NOLINT
    void DIF(int *a, int n) {
        for (int k = n >> 1; k; k >>= 1)
            for (int i = 0, y; i < n; i += k << 1)
                fp(j, 0, k - 1)y = a[i + j + k], a[i + j + k] = MUL(a[i + j] - y + P, W[k + j]), ADD(a[i + j], y);
    }

    void IDIT(int *a, int n) {
        for (int k = 1; k < n; k <<= 1)
            for (int i = 0, x, y; i < n; i += k << 1)
                fp(j, 0, k - 1)
                    x = a[i + j], y = MUL(a[i + j + k], W[k + j]),
                            a[i + j + k] = x - y < 0 ? x - y + P : x - y, ADD(a[i + j], y);
        int Inv = P - (P - 1) / n;
        fp(i, 0, n - 1) a[i] = MUL(a[i], Inv);
        reverse(a + 1, a + n);
    }
}
namespace Polynomial {
    using Poly = std::vector<int>;

    // mul/div int
    Poly &operator*=(Poly &a, int b) {
        for (auto &x: a) x = MUL(x, b);
        return a;
    }

    Poly operator*(Poly a, int b) { return a *= b; }

    Poly operator*(int a, Poly b) { return std::move(b) * a; }

    Poly &operator/=(Poly &a, int b) { return a *= POW(b); }

    Poly operator/(Poly a, int b) { return a /= b; }

    // Poly add/sub
    Poly &operator+=(Poly &a, Poly b) {
        a.resize(max(a.size(), b.size()));
        fp(i, 0, b.size() - 1) ADD(a[i], b[i]);
        return a;
    }

    Poly operator+(Poly a, Poly b) { return a += std::move(b); }

    Poly &operator-=(Poly &a, Poly b) {
        a.resize(max(a.size(), b.size()));
        fp(i, 0, b.size() - 1) SUB(a[i], b[i]);
        return a;
    }

    Poly operator-(Poly a, Poly b) { return a -= std::move(b); }

    // Poly mul
    void DFT(Poly &a) { NTT::DIF(a.data(), a.size()); }

    void IDFT(Poly &a) { NTT::IDIT(a.data(), a.size()); }

    int norm(int n) { return 1 << (32 - __builtin_clz(n - 1)); }

    void norm(Poly &a) { if (!a.empty()) a.resize(norm(a.size()), 0); }

    Poly &dot(Poly &a, Poly &b) {
        fp(i, 0, a.size() - 1) a[i] = MUL(a[i], b[i]);
        return a;
    }

    Poly operator*(Poly a, Poly b) {
        int n = a.size() + b.size() - 1, L = norm(n);
        if (a.size() <= 8 || b.size() <= 8) {
            Poly c(n);
            fp(i, 0, a.size() - 1) fp(j, 0, b.size() - 1)c[i + j] = (c[i + j] + (ll) a[i] * b[j]) % P;
            return c;
        }
        a.resize(L), b.resize(L);
        DFT(a), DFT(b), dot(a, b), IDFT(a);
        return a.resize(n), a;
    }

    // Poly inv
    Poly Inv2k(Poly a) { // a.size() = 2^k
        int n = a.size(), m = n >> 1;
        if (n == 1) return {POW(a[0])};
        Poly b = Inv2k(Poly(a.begin(), a.begin() + m)), c = b;
        b.resize(n), DFT(a), DFT(b), dot(a, b), IDFT(a);
        fp(i, 0, n - 1) a[i] = i < m ? 0 : P - a[i];
        DFT(a), dot(a, b), IDFT(a);
        return move(c.begin(), c.end(), a.begin()), a;
    }

    Poly Inv(Poly a) {
        int n = a.size();
        norm(a), a = Inv2k(a);
        return a.resize(n), a;
    }

    // Poly div/mod
    Poly operator/(Poly a, Poly b) {
        int k = a.size() - b.size() + 1;
        if (k < 0) return {0};
        reverse(a.begin(), a.end());
        reverse(b.begin(), b.end());
        b.resize(k), a = a * Inv(b);
        a.resize(k), reverse(a.begin(), a.end());
        return a;
    }

    pair<Poly, Poly> operator%(Poly a, const Poly &b) {
        Poly c = a / b;
        a -= b * c, a.resize(b.size() - 1);
        return {c, a};
    }

}
const int mod = 998244353;

int ksm(int x, int k) {
    int res = 1;
    while (k) {
        if (k & 1)res = res * x % mod;
        x = x * x % mod;
        k /= 2;
    }
    return res;
}

int ny(int x) {
    return ksm(x, mod - 2);
}

void add(int &x, int y) {
    if ((x += y) >= mod)x -= mod;
}

using namespace Polynomial;
string s[maxn];
int l[maxn], r[maxn];
int cf[maxn], sum[maxn];
Poly gg;

void suregg(int len) {
    while ((int) gg.size() < len) {
        gg.push_back(gg.back() + cf[(int) gg.size()]);
    }
}

int caneat[maxn];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    gg.push_back(0);
    int m, n;
    cin >> m >> n;
    for (int i = 1; i <= m; i++)cin >> l[i] >> r[i], cf[l[i]]++, cf[r[i] + 1]--;
    int all = 210000;
    for (int i = 1; i <= n; i++) {
        string ss;
        int x;
        cin >> ss >> x;
        s[x] = ss;
    }
    int now = 0;
    int pr = 0;
    vector<pair<int, int>> Intval;
    for (int i = 1; i <= all; i++) {
        if (s[i] == "add") {
            now++;
        } else if (s[i] == "pop") {
            now--;
            if (now == 0) {
                Intval.emplace_back(pr + 1, i);
                pr = i;
            }
        }
    }
    if (pr != all)Intval.emplace_back(pr + 1, all);
    sort(Intval.begin(), Intval.end(), [&](pair<int, int> p1, pair<int, int> p2) {
        return p1.second - p1.first < p2.second - p2.first;
    });
    int ans = 0;
    function<void(int, int)> solve = [&](int l, int r) {
        int len = r - l + 1;
        suregg(len);
        for (int i = 0; i <= len; i++)sum[i] = 1, caneat[i] = 0;
        Poly a(len + 1, 0);
        int now = 0;
        for (int i = l; i <= r; i++) {
            if (s[i] == "add")now++;
            else {
                if (s[i] == "pop") {
                    caneat[i - l + 1] = ny(now) * sum[i - 1 - l + 1] % mod;
                    sum[i - l + 1] = (1 - ny(now) + mod) % mod;
                    now--;
                }

            }
            sum[i - l + 1] = sum[i - l + 1] * sum[i - l] % mod;
        }
        for (int i = l; i <= r; i++)if (s[i] == "add")a[i - l + 1] = ny(sum[i - l]);
        a = a * gg;
        for (int i = 1; i <= len; i++) {
            add(ans, a[i] * caneat[i] % mod);
        }
    };
    for (auto [g, f]: Intval) {
        solve(g, f);
    }
    cout << ans * ny(m) % mod << "\n";
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值