A.青春常数

答案:1013101260121012

解析:送分

B.双碳战略

答案:792264670

解析:典型的找规律的题目。正常暴力解练笔都动不了。

我们首先通过小规模的 nn 来寻找“最少操作次数总和”的规律。

  • 当 n=1 时,状态有 2 种,总步数为 1。公式 1×2^0=1 。
  • 当 n=2 时,状态有 4 种,总步数为 4。公式 2×2^1=4 。
  • 当 n=3 时,状态有 8 种,总步数为 12。公式 3×2^{_{2}}=12 。

由此归纳出通项公式:对于 n 盏路灯,所有状态的最少操作次数之和为:

S_n = n \times 2^{n-1}

规律找出来了。接下来还需要考虑的是如何计算这么大的指数形式的数的值,这里很自然可以想到快速幂模版。

// 快速幂函数:计算 (base^exp) % mod
long long modPow(long long base, long long exp, long long mod) {
    long long result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) { // 如果 exp 是奇数
            result = (result * base) % mod;
        }
        base = (base * base) % mod;
        exp >>= 1; // exp 右移一位 (除以 2)
    }
    return result;
}

最后输出结果即可。

#include <iostream>

using namespace std;
int main() {
    const long long MOD = 998244353;
    const long long n = 2026;
    
    // 计算公式:n * 2^(n-1) % MOD
    long long power_part = modPow(2, n - 1, MOD);
    long long ans = (n % MOD) * power_part % MOD;
    
    cout << ans << endl;
    
    return 0;
}

C.循环右移

题目描述

给定三个整数 N,X,Y。请计算有多少个长度为 N 的整数数组 A 满足以下条件:

  1. 数组 A 中的每个元素 Ai​ 都满足 X≤Ai​≤Y;
  2. 对于数组 A 中的任意一个连续子数组,对其进行一次循环右移操作,得到的新子数组与原数组完全一致。

循环右移:对一个长度为 k 的连续子数组 [B1​,B2​,…,Bk​] 执行一次循环右移操作,是指将该子数组变换为 [Bk​,B1​,B2​,...,Bk−1​](即把最后一个元素移到最开头,其余元素保持原有顺序依次向后顺延一位)。

输入格式

第一行包含一个整数 T,表示测试数据的组数。

接下来的 T 行,每行包含三个由空格隔开的整数 N,X,Y。

输出格式

对于每组测试数据,输出一行,包含一个整数,表示满足条件的数组 A 的个数。

答案:

#include <iostream>

using namespace std;

int main() {
 
    int T;
    if (cin >> T) {
        while (T--) {
            long long N, X, Y;
            cin >> N >> X >> Y;

            long long ans = 0;
            
            // 核心逻辑:数组必须全相等,即所有元素取值 v 必须满足 X <= v <= Y
            if (X <= Y) {
                ans = Y - X + 1;
            } else {
                ans = 0;
            }

            cout << ans << "\n";
        }
    }

    return 0;
}

解析:关键就是题目中的条件等价于数组 A 的任意连续子数组中的所有元素都必须相等。

D. 蓝桥竞技

题目描述

小蓝,作为电竞俱乐部“蓝桥竞技”的战队经理,正面临着一个巨大的管理危机。俱乐部目前签约了 N 种不同位置的职业选手,其中第 i 种位置的选手共有 Ai​ 名。

为了参加即将举办的“峡谷 5v5”,小蓝必须将俱乐部内的所有选手都编入战队,不能有任何一人坐冷板凳。

根据赛事组委会的严苛规则,一支合法的战队必须满足以下条件:

  1. 5 人成团:每支战队由且只能由 5 名选手组成。
  2. 职业互斥:同一支战队内的 5 名选手,必须来自 5 种完全不同的位置。

现在,请你帮助小蓝判断:在当前的人员数量下,是否有一种分组方案,能够将所有选手恰好分配完,且每支战队都符合参赛规则?

输入格式

第一行包含一个整数 T,表示测试用例组数。

接下来包含 T 组数据,每组数据的格式如下:

  • 第一行包含一个整数 N,表示职业位置的种类数量。
  • 第二行包含 N 个整数 A1​,A2​,…,AN​,分别表示第 i 种位置的选手人数。

输出格式

对于每组测试用例,如果存在满足条件的分组方案,输出 T,否则输出 F。

答案:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {


    int T;
    cin >> T;

    while (T--) {
        int N;
        cin >> N;
        vector<long long> A(N);
        long long sum = 0;
        for (int i = 0; i < N; i++) {
            cin >> A[i];
            sum += A[i];
        }

        // 条件1: 至少5种位置
        if (N < 5) {
            cout << "F\n";
            continue;
        }

        // 条件2: 总人数是5的倍数
        if (sum % 5 != 0) {
            cout << "F\n";
            continue;
        }

        long long k = sum / 5; // 战队数量

        // 条件3: 每个位置人数不超过战队数
        bool valid = true;
        for (int i = 0; i < N; i++) {
            if (A[i] > k) {
                valid = false;
                break;
            }
        }

        cout << (valid ? "T" : "F") << "\n";
    }

    return 0;
}

解析:每支战队需要 5 个不同位置,所以 N ≥ 5,

否则连一支战队都组不起来。这是一个经典的二分图匹配或多重集合分组问题。看代码自己理解。

E. LQ 聚合

题目描述

2056 年,探险队在月球背面的环形山深处发现了一座信号发射塔,其核心控制台正在不断闪烁着一串长度为 N 的粒子序列。

序列中的每个位置被严格定义为 L 型粒子、Q 型粒子,或者因岁月侵蚀而模糊不清的未知状态 ?。这些粒子将被依次射入反应场,而反应场的稳定性取决于序列的“LQ 聚合”数量,该数量被定义为所有满足 1≤i<j≤N 且第 i 个节点为 L、第 j 个节点为 Q 的二元组数量。

为了重启这座沉睡的巨塔,探险队需要将序列中所有的 ? 修复为确定的 L 或 Q。

现在,请你计算出在所有可能的修复方案中,所能得到的“LQ 聚合”数量的最大值是多少。

输入格式

第一行输入一个整数 N,表示粒子序列的长度。

第二行输入一个长度为 N 的字符串,仅包含字符 L、Q 和 ?,表示当前探测到的粒子序列状态。

输出格式

输出一个整数,表示在将所有 ? 替换为 L 或 Q 后,能获得的最大“LQ 聚合”数量。

答案及注释:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    // 优化输入输出效率,防止大数据量下超时
    ios::sync_with_stdio(false);
    cin.tie(0);

    int N;
    if (!(cin >> N)) return 0;
    string s;
    cin >> s;

    // --- 第一步:预处理 ---
    
    // 1. 统计 ? 的总数,用于后续计算右边还有多少个 ? 会变成 Q
    long long total_unknown = 0;
    for (char c : s) {
        if (c == '?') total_unknown++;
    }

    // 2. 计算初始状态的分数
    // 假设分界线在最左边(k=0),即所有 ? 都暂时看作 Q
    long long current_pairs = 0;
    long long count_L = 0; // 记录当前扫描过的 L 的数量
    
    for (char c : s) {
        if (c == 'L') {
            count_L++;
        } else if (c == 'Q' || c == '?') {
            // 如果是 Q 或者 ? (暂时看作 Q),它们会与左边所有的 L 形成配对
            current_pairs += count_L;
        }
    }

    long long max_pairs = current_pairs; // 初始化最大值为初始状态

    // 3. 预处理后缀 Q 的数量
    // suffix_Q[i] 表示从位置 i 到末尾,原本就是 'Q' 的字符数量
    vector<long long> suffix_Q(N + 1, 0);
    for (int i = N - 1; i >= 0; i--) {
        suffix_Q[i] = suffix_Q[i + 1] + (s[i] == 'Q' ? 1 : 0);
    }

    // --- 第二步:贪心移动分界线 ---
    
    long long left_L = 0;          // 分界线左边 L 的总数(包括原本的 L 和 ? 变成的 L)
    int unknown_converted_to_L = 0; // 已经被变成 L 的 ? 的数量

    // 从左到右扫描,尝试将分界线向右移动
    // 当扫描到 s[i] 为 ? 时,意味着分界线跨过它,它从 Q 变成了 L
    for (int i = 0; i < N; i++) {
        if (s[i] == 'L') {
            // 遇到原本的 L,它被包含在左边的 L 集合中
            left_L++;
        } else if (s[i] == '?') {
            // --- 核心逻辑:将 s[i] 从 Q 变成 L ---
            
            // 1. 失去的贡献:
            // 它之前作为 Q,与左边所有的 L (left_L) 都有配对。
            // 现在变成了 L,这些配对消失了。
            current_pairs -= left_L;
            
            // 2. 获得的贡献:
            // 它现在作为 L,将与右边所有的 Q 形成新配对。
            // 右边的 Q 由两部分组成:
            // A. 右边原本就是 Q 的字符 (suffix_Q[i + 1])
            // B. 右边还没被扫描到的 ? (它们依然保持为 Q)
            long long right_original_Q = suffix_Q[i + 1];
            long long right_unknown_Q = (total_unknown - unknown_converted_to_L - 1);
            
            current_pairs += (right_original_Q + right_unknown_Q);
            
            // 3. 更新状态变量
            left_L++; // 这个 ? 现在是 L 了,加入左边 L 的计数
            unknown_converted_to_L++; // 已转换的 ? 数量 +1
            
            // 4. 更新最大值
            max_pairs = max(max_pairs, current_pairs);
        }
    }

    cout << max_pairs << endl;
    return 0;
}

F.应急布线

题目描述

实验室内,小蓝负责维护一个由 N 台计算机组成的局域网络。这些计算机原本通过高速网线互联,但随着设备老化,目前仅剩 M 条网线还能正常工作。正因如此,原本统一的网络分裂成了多个互不相连的通信区域。

为了恢复通信,小蓝计划架设一种特殊的“应急跳线”来重新连接这些区域。在物理逻辑上,只要任意两台计算机之间可以通过残存的网线或新架设的应急跳线建立直接或间接的通路,即视为它们之间恢复了连通。小蓝的任务是选定最优的布线方案,使得实验室内的所有 N 台计算机重新回到全连通的状态。

由于应急跳线的接口会占用计算机宝贵的硬件资源,小蓝在制定方案时设定了两个优先层级:首先,必须确保使用的应急跳线总数达到理论上的最小值;其次,为了减轻单台计算机的负载压力,他要求尽可能平均地分摊这些跳线。具体而言,他需要寻找一种方案,使得在所有计算机中,接入这种应急跳线数量最多的那一台机器,其接入的跳线数降到最低。

现在,请你根据当前网线的残存连接情况,计算出实现全连通所需的最少应急跳线数量,以及在这一最优前提下,单台计算机接入应急跳线数量最大值的最小可能值。

输入格式

第一行包含两个整数 N 和 M,分别表示计算机的总数和目前完好的网线数量。

接下来的 M 行,每行包含两个整数 a 和 b,表示计算机 a 和 b 之间目前仍存在一条完好的网线。

输出格式

输出一行,包含两个由单个空格隔开的整数。第一个整数表示最少需要添加的应急跳线数量,第二个整数表示在确保跳线总数最少的前提下,单台计算机接入跳线数量最大值的最小可能值。

答案及解析:

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>

using namespace std;

// 并查集模板
class UnionFind {
    vector<int> parent;
    vector<int> size; // 记录每个连通分量的大小
    int components;

public:
    UnionFind(int n) {
        components = n;
        parent.resize(n);
        size.resize(n, 1);
        iota(parent.begin(), parent.end(), 0);
    }

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        
        if (rootX != rootY) {
            // 按大小合并
            if (size[rootX] < size[rootY]) {
                parent[rootX] = rootY;
                size[rootY] += size[rootX];
            } else {
                parent[rootY] = rootX;
                size[rootX] += size[rootY];
            }
            components--;
        }
    }

    int getComponentCount() {
        return components;
    }
};

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

    int N, M;
    if (!(cin >> N >> M)) return 0;

    UnionFind uf(N);

    for (int i = 0; i < M; i++) {
        int a, b;
        cin >> a >> b;
        uf.unite(a - 1, b - 1); // 转换为0-based
    }

    int k = uf.getComponentCount();

    int min_edges = (k <= 1) ? 0 : k - 1;
    
    int min_max_degree;
    if (k <= 1) {
        min_max_degree = 0;
    } else if (k == 2) {
        min_max_degree = 1;
    } else {
        // k >= 3
        // 我们可以构造一条链来连接所有分量
        // 链中间的节点度数为2,两端为1
        // 所以最大度数为2
        min_max_degree = 2;
    }

    cout << min_edges << " " << min_max_degree << endl;

    return 0;
}

G.理想温度

题目描述

一条工业流水线上排列着 n 个温度传感器。当前各个传感器测得的温度记录在数组 A 中,而各传感器对应的理想标准温度记录在数组 B 中(即 Ai​ 为第 i 个传感器的当前温度,Bi​ 为第 i 个传感器的理想温度)。

为了让尽可能多的传感器达到理想温度,你可以进行一次区域温度补偿操作:

  1. 在流水线上划定一段连续的传感器区间 [l,r](即第 l 个到第 r 个传感器)。
  2. 输入一个温度补偿值 k(k 为任意整数),使得该区间内所有传感器的当前温度都加上 k。

请问在执行完这一次校准操作后,最多能使多少个传感器的温度恰好等于其对应的理想标准温度?

输入格式

第一行包含一个整数 n,表示传感器的数量。

第二行包含 n 个整数 A1​,A2​,…,An​,表示各传感器的当前温度。

第三行包含 n 个整数 B1​,B2​,…,Bn​,表示各传感器对应的理想标准温度。

输出格式

输出一行,包含一个整数,表示补偿操作后处于理想温度的传感器最大数量。

答案:

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
using namespace std;

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

    int n;
    cin >> n;

    vector<int> A(n), B(n);
    for (int i = 0; i < n; i++) {
        cin >> A[i];
    }
    for (int i = 0; i < n; i++) {
        cin >> B[i];
    }

    // 计算差值数组 D
    vector<int> D(n);
    for (int i = 0; i < n; i++) {
        D[i] = B[i] - A[i];
    }

    // 计算基础分
    int base = 0;
    for (int i = 0; i < n; i++) {
        if (D[i] == 0) {
            base++;
        }
    }

    if (base == n) {
        cout << n << endl;
        return 0;
    }

    int ans = base;

    // 统计每个 k 的出现次数
    unordered_map<int, int> count;
    for (int i = 0; i < n; i++) {
        if (D[i] != 0) {
            count[D[i]]++;
        }
    }

    // 将 k 值按出现次数从大到小排序
    vector<pair<int, int>> candidates(count.begin(), count.end());
    sort(candidates.begin(), candidates.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
        return a.second > b.second;
    });

    // 只枚举出现次数最多的前 100 个 k 值
    // 这是一个启发式优化,通常能过
    int limit = min((int)candidates.size(), 100);
    for (int i = 0; i < limit; i++) {
        int k = candidates[i].first;
        
        // Kadane 算法
        int max_ending_here = 0;
        int max_so_far = 0;
        
        for (int j = 0; j < n; j++) {
            int val = 0;
            if (D[j] == k) {
                val = 1;
            } else if (D[j] == 0) {
                val = -1;
            }
            
            max_ending_here = max(0, max_ending_here + val);
            max_so_far = max(max_so_far, max_ending_here);
        }
        
        ans = max(ans, base + max_so_far);
    }

    cout << ans << endl;
    return 0;
}

H.足球训练

题目描述

小蓝是一支足球队的队长,他正在为下一场重要比赛做准备。接下来的 m 天中,他每天可以选择一名队员进行训练,并且选择之后当天的训练对象不能更换。

球队中共有 n 名队员。对于第 i 名队员,已知其:

  • 初始实力值为 ai​;
  • 天赋值为 bi​。

训练规则如下:

  • 如果小蓝在某一天训练了队员 i,则这一天会使该队员的实力值增加 bi​;
  • 如果一共用 k 天来训练队员 i,那么这名队员的最终实力值将变为:ai​+kbi​。

一支队伍的整体实力定义为所有队员最终实力值的乘积,即:

i=1∏n​(ai​+ki​bi​)

其中 ki​ 表示分配给第 i 名队员的训练天数,且满足:

ki​≥0,i=1∑n​ki​=m

小蓝希望通过合理分配这 m 天的训练计划,使得队伍的整体实力最大。由于结果可能非常大,你只需要输出该最大值对 998244353 取模的结果。

答案:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const long long MOD = 998244353;

struct Player {
    long long a, b, c; // c = a / b
    // 用于排序,比较边际收益 b / (a + k*b)
    // 比较 b1/(a1+k1*b1) 和 b2/(a2+k2*b2)
    // 这里我们只需要比较 b/a 即可,因为 k 是固定的
    bool operator < (const Player& other) const {
        return b * other.a > a * other.b;
    }
};

int main() {
  
    int n;
    long long m;
    cin >> n >> m;

    vector<Player> players(n);
    for (int i = 0; i < n; i++) {
        cin >> players[i].a >> players[i].b;
        players[i].c = players[i].a / players[i].b;
    }

    // 二分 T = floor(1/r)
    // check(x) 检查当 T=x 时,总训练次数是否 <= m
    auto check = [&](long long x) {
        long long total_days = 0;
        for (int i = 0; i < n; i++) {
            if (players[i].c < x) {
                total_days += x - players[i].c;
                if (total_days > m) return false;
            }
        }
        return true;
    };

    long long l = 0, r = 2000000000LL, best_T = 0;
    while (l <= r) {
        long long mid = (l + r) / 2;
        if (check(mid)) {
            best_T = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }

    // 根据 best_T 分配训练次数
    long long used_days = 0;
    for (int i = 0; i < n; i++) {
        if (best_T > players[i].c) {
            long long k = best_T - players[i].c;
            players[i].a += k * players[i].b;
            used_days += k;
        }
    }

    // 处理剩余天数
    long long remaining_days = m - used_days;
    sort(players.begin(), players.end()); // 按边际收益 b/a 降序排序

    for (int i = 0; i < remaining_days; i++) {
        players[i].a += players[i].b;
    }

    // 计算最终乘积
    long long result = 1;
    for (int i = 0; i < n; i++) {
        result = (result * (players[i].a % MOD)) % MOD;
    }

    cout << result << endl;

    return 0;
}

希望对你有帮助。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐