16届蓝桥杯B组 C++题解

16届蓝桥杯B组 C++题解

问题1(填空题)

问题描述

小明初始在二维平面的原点 (0,0)(0,0),他想前往坐标 (233,666)。

在移动过程中,他只能采用以下两种移动方式,并且这两种移动方式可以交替、不限次数地使用:

  1. 水平向右移动,即沿着 xx 轴正方向移动一定的距离。
  2. 沿着一个圆心在原点 (0,0)(0,0)、以他当前位置到原点的距离为半径的圆的圆周移动,移动方向不限(即顺时针或逆时针移动不限)。

在这种条件下,他到达目的地最少移动多少单位距离?

只需输出答案四舍五入到整数的结果。

问题关键

  1. 水平移动仅改变横坐标,而且开头第一次移动必须是水平移动;

  2. 最优情况下,到达目标点的最后一段移动绝对是弧(反证法易证);

  3. 最后那段弧,必然属于以(0,0)为原点,以原点到目标点的直线距离为半径的圆;

  4. 总结1与2最优路径必然是两段式(先水平移动,再圆周移动);

    • 多段交替移动(如 "水平→圆周→水平→圆周")不会产生更短的路径。

    • 数学上可以证明:任何多段路径的总距离,都大于或等于 "先水平移动到目标点所在圆的 x 轴交点,再沿圆弧到目标点" 的两段式距离。

    • 本质原因:额外的水平移动会增加径向距离,额外的圆周移动会增加角度距离,两者都会让总距离变大。

  5. 最后一段弧的圆心角等于目标点的极角 θ ;

数值计算

  • 目标点到原点的距离:R=2332 +6662 ≈705.58
  • 目标点的夹角:θ=arctan(233/666)≈1.234 弧度
  • 总移动距离:r1+r1⋅θ1=r1⋅(1+θ1)≈1576.45

结果

1576


问题2(填空题)

问题描述

一家连锁旅馆在全国拥有 2025 个分店,分别编号为 1 至 2025。随着节日临近,总部决定为每家分店设定每日客流量的上限,分别记作 A1,A2,...,A2025。这些上限并非随意分配,而是需要满足以下约束条件:

  • A1,A2,...,A2025 必须是 11 至 2025 的一个排列,即每个 Ai均是 1 至 2025 之间的整数,且所有 Ai 互不相同。
  • 对于任意分店 ii和 j(1≤i,j≤20251≤i,j≤2025,i可等于 j),它们的客流量上限 Ai 和 Aj 的乘积不得超过 ij+2025。

这些约束旨在平衡各分店客流压力,确保服务质量和运营稳定性。

现在,请你计算这样的分配方案究竟有多少种。由于答案可能很大,你只需输出其对 109+7取余后的结果即可。

问题关键

  1. i可以等于j;
  2. 2025必须放在A2025上,否则Ai* Aj<=2025+ij不成立,例如:A2024=2025时,2025 * 2025 > 20242024+2025;
  3. 同2可证:i>=1013时,Ai = i;
  4. i < 1013时,Ai=i+1 , i , i-1 , ...... , 1 ;从前往后考虑,A1 有 1、2 两种选法,A2 在 A1 选过之后,还有 2 种选法;A3在前两个选完之后,还有 2 种选法;...;A1012在前面都选完之后,只剩下 2 种选法 。因此,总共的方案数为 21012 ;

数值计算

  • 21012 % ( 109 +7 )

    cpp 复制代码
    for(int i = 1; i <= 1012; i++){
        result = result * 2 % mod;
    }

结果

781448427


问题3

问题描述

定义一种特殊的整数序列:这种序列由连续递增的整数组成,并满足以下条件:

  1. 序列长度至少为 3。
  2. 序列中的数字是连续递增的整数(即相邻元素之差为 1),可以包括正整数、负整数或 0。

例如,[1,2,3][1,2,3]、[4,5,6,7][4,5,6,7] 和 [−1,0,1][−1,0,1] 是符合条件的序列,而 [1,2][1,2](长度不足)和 [1,2,4][1,2,4](不连续)不符合要求。

现给定一组包含 NN 个正整数的数据 A1,A2,...,AN。如果某个 Ai 能够表示为符合上述条件的连续整数序列中所有元素的和,则称 Ai 是可分解的。

请你统计这组数据中可分解的正整数的数量。

输入格式

输入的第一行包含一个正整数 N,表示数据的个数。

第二行包含 N 个正整数 A1,A2,...,AN,表示需要判断是否可分解的正整数序列。

输出格式

输出一个整数,表示给定数据中可分解的正整数的数量。

样例输入

复制代码
3
3 6 15

样例输出

复制代码
3

样例说明

  • Ai=3是可分解的,因为 [0,1,2][0,1,2] 的和为 0+1+2=3。
  • Ai=6 是可分解的,因为 [1,2,3][1,2,3] 的和为 1+2+3=6。
  • Ai=15是可分解的,因为 [4,5,6][4,5,6] 的和为 4+5+6=15。

所以可分解的正整数的数量为 3。

评测用例规模与约定

对于 30% 的评测用例,1≤N≤100,1≤Ai≤100。

对于所有评测用例,1≤N≤105 ,1≤Ai≤109

问题分析

  • 除1之外的所有数都满足,(k!=1)
    k=−(k−1)−(k−2)+......+0+1+2+......+k k= -(k-1) - (k-2) +......+ 0 + 1 + 2 +......+k k=−(k−1)−(k−2)+......+0+1+2+......+k

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >>n;
    vector<long long> a(n);
    int ans=0;
    for(int i=0;i<n;++i){
        cin >>a[i];
        if(a[i]!=1){  //不是1就行
            ans++;
        }
    }
    cout <<ans;
    return 0;
}

问题4

问题描述

偏远的小镇上,三兄弟共同经营着一家小型矿业公司"兄弟矿业"。公司旗下有三座矿山:金矿、银矿和铜矿,它们的初始产值分别用非负整数 AA、BB 和 CC 表示。这些矿山的产出是小镇经济的核心,支撑着三兄弟和许多矿工家庭的生计。

然而,各矿山的产值波动剧烈,有时金矿收益高而银矿、铜矿低迷,有时则相反。这种不稳定性让公司收入难以预测,也常引发兄弟间的争执。为了稳定经营,三兄弟设计了一个公平的产值调整策略,每年执行一次,每次调整时,将根据当前的产值 AA、BB、CC,计算新产值:

  • 金矿新产值 A′=⌊B+C2⌋A'=⌊\frac{B+C}{2}⌋A′=⌊2B+C⌋;
  • 银矿新产值 B′=⌊A+C2⌋B'=⌊\frac{A+C}{2}⌋B′=⌊2A+C⌋;
  • 铜矿新产值 C′=⌊B+A2⌋C'=⌊\frac{B+A}{2}⌋C′=⌊2B+A⌋。

其中,⌊ ⌋ 表示向下取整。例如,⌊3.7⌋=3,⌊5.2⌋=5。

计算出 A′、B′、C′ 后,同时更新:A 变为 A′,B 变为 B′,C 变为 C′,作为下一年调整的基础。

三兄弟认为这个方法能平衡产值波动,于是计划连续执行 K 次调整。现在,请你帮他们计算,经过 KK 次调整后,金矿、银矿和铜矿的产值分别是多少。

输入格式

输入的第一行包含一个整数 T ,表示测试用例的数量。

接下来的 T 行,每行包含四个整数 A,B,C,K,分别表示金矿、银矿和铜矿的初始产值,以及需要执行的调整次数。

输出格式

对于每个测试用例,输出一行,包含三个整数,表示经过 KK 次调整后金矿、银矿和铜矿的产值,用空格分隔。

样例输入

复制代码
2
10 20 30 1
5 5 5 3

样例输出

复制代码
25 20 15
5 5 5

评测用例规模与约定

对于 30% 的评测用例,1≤T≤100,1≤A,B,C,K≤105

对于所有评测用例,1≤T≤105 ,1≤A,B,C,K≤109

问题关键

  • A=B=C 时停止循环;

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int main(){
    // 输入优化:提升输入输出速度
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;
    cin >>T;
    while(T--){
        long long a,b,c,k;
        cin >>a>>b>>c>>k;
        for(int i=0;i<k;++i){
            long long A,B,C;
            A=(b+c)/2;
            B=(a+c)/2;
            C=(a+b)/2;
            a=A;
            b=B;
            c=C;
            if(a==b&&b==c) break; /* 关键,不加后果:共20个测                                      试用例,10个通过,10个未                                      通过 */
        }
        cout <<a<<" "<<b<<" "<<c<<'\n';
    }
    return 0;
}

问题5

问题描述

画展策展人小蓝和助理小桥为即将举办的画展准备了 N 幅画作,其艺术价值分别为 A1,A2,...,AN。他们需要从这 NN 幅画中挑选 M 幅,并按照一定顺序布置在展厅的 M 个位置上。如果随意挑选和排列,艺术价值的变化可能会过于突兀,导致观众的观展体验不够流畅。

为了优化布置,他们查阅了《画展布置指南》。指南指出,理想的画展应使观众在欣赏画作时,艺术价值的过渡尽量平缓。指南建议,选择并排列 M 幅画,应使艺术价值的变化程度通过一个数值 L 来衡量,且该值越小越好。数值 L 的定义为:
L=∑i=1M−1∣Bi+12−Bi2∣ L = \sum_{i=1}^{M-1}| B^2_{i+1}- B^2_{i} | L=i=1∑M−1∣Bi+12−Bi2∣

其中 Bi表示展厅第 i 个位置上画作的艺术价值。

现在,他们希望通过精心挑选和排列这 MM 幅画作,使 LL 达到最小值,以提升画展的整体协调性。请你帮他们计算出这个最小值是多少。

输入格式

输入共两行。

第一行包含两个正整数 N 和 M,分别表示画作的总数和需要挑选的画作数量。

第二行包含 N 个正整数 A1,A2,...,AN,表示每幅画作的艺术价值。

输出格式

输出一个整数,表示 LL 的最小值。

样例输入

复制代码
4 2
1 5 2 4

样例输出

复制代码
3

评测用例规模与约定

对于 40% 的评测用例,2≤M≤N≤103 ,1≤Ai≤103

对于所有评测用例,2≤M≤N≤105 , 1≤Ai≤105

问题关键

  1. 排序消除绝对值,方便后续操作;

  2. 最优子数组 具有连续性

    假设原数组已经排序为: a1<=a2<=......<=aNa_1<=a_2<=......<=a_Na1<=a2<=......<=aN

    • 如果我们选不连续 的 M 个数(比如 a1,a3,a4a_1 ,a_3,a_4a1,a3,a4 ),相邻平方差之和是 :(a32−a12)+(a42−a32)=(a42−a12)(a^2_3-a^2_1)+(a^2_4-a^2_3)=(a^2_4-a^2_1)(a32−a12)+(a42−a32)=(a42−a12);

    • 如果我们选连续 的 M 个数(比如 a1,a2,a3a_1 ,a_2,a_3a1,a2,a3),相邻平方差之和是 :(a22−a12)+(a32−a22)=(a32−a12)(a^2_2-a^2_1)+(a^2_3-a^2_2)=(a^2_3-a^2_1)(a22−a12)+(a32−a22)=(a32−a12);

      显然,连续子数组的平方差之和更小。因此,排序后,只有连续的 M 个元素才能让 L 最小。

  3. 滑动窗口高效求解 ;

  4. 由于艺术价值是正整数,平方后数值可能很大,必须用 long long 存储平方差和窗口和,防止溢出。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

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

    int n, m;
    cin >> n >> m;
    vector<int> a(n);  // 存储所有画作的艺术价值
    for (int i = 0; i < n; ++i) cin >> a[i];

    // 核心前提:排序后才能消除绝对值,且连续子数组的平方差和最小
    sort(a.begin(), a.end());

    // 相邻平方差数组:diff[i] = a[i+1]² - a[i]²
    vector<long long> diff(n - 1);
    for (int i = 0; i < n - 1; ++i) {
        // 1LL强制转换为长整型:防止int平方后溢出
        diff[i] = 1LL * a[i + 1] * a[i + 1] - 1LL * a[i] * a[i];
    }

    long long window = 0;// 滑动窗口:存储当前窗口内的平方差之和
    long long ans = LLONG_MAX; // 初始化为long long的最大值

    // 滑动窗口遍历diff数组(窗口大小为m-1)
    for (int i = 0; i < n - 1; ++i) {
        window += diff[i];     // 新元素加入窗口

        // 窗口大小超过m-1时,移除窗口最左侧的元素
        if (i >= m - 1) {                   
            window -= diff[i - (m - 1)];
        }

        // 当i >= m-2时,窗口内刚好有m-1个差值(对应m个连续元素)
        
        if (i >= m - 2) {                   
            ans = min(ans, window);  // 更新最小平方差和
        }
    }

    cout << ans;  // 输出最小的L值
    return 0;
}

问题6

问题描述

小明需要在一条 2×n 的河床上铺设水质检测器。在他铺设之前,河床上已经存在一些检测器。如果两个检测器上下或左右相邻,那么这两个检测器就是互相连通的。

连通具有传递性,即如果 A 和 B 连通,B 和 C 连通,那么 A 和 C 也连通。现在他需要在河床上增加铺设一些检测器,使得所有检测器都互相连通。他想知道最少需要增加铺设多少个检测器?

输入格式

输入共两行,表示一个 2×n 的河床。

每行一个长度为 n 的字符串,仅包含 #., 其中 # 表示已经存在的检测器,. 表示空白。

输出格式

输出共 11 行,一个整数,表示最少需要增加的检测器数量。

样例输入

复制代码
.##.....#
.#.#.#...

样例输出

复制代码
5

样例说明

其中一种方案: .###....# .#.######

增加了 5 个检测器。

评测用例规模与约定

对于 100% 的评测用例,保证 n≤1000000。

问题关键

  1. 状态编码 :用cal函数将每列的#分布编码为 3 种状态(1:上 #下.;2:上。下 #;3:上下 #) ;
  2. 扫描列 :从左到右遍历所有列,仅关注 "非空列"(含#的列) ;
  3. 连通非空列 :记录上一个非空列的位置与状态,通过 "当前列与上一非空列的状态关系" 计算中间全空列需新增的#数量:
    • 若状态是1 和 2 互斥 (上一列为上 #、当前为下 #,或反之):需 "打通上下",新增i-lst#(等价于中间全空列数 + 1),并将当前状态更新为 3(上下连通)。
    • 若状态兼容(如均为 1、均为 2、含 3):仅需铺中间全空列的对应行,新增i-lst-1#

代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;

string a,b;

int cal(char x,char y)
{
    if(x=='#'&&y=='.') return 1;
    else if(x=='.'&&y=='#') return 2;
    else return 3;  // '# #' 的情况
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin>>a>>b;
    int n=a.size();
    /*
    ans   : 最终需要补充的检测器数量(答案)
    lst   : 上一次出现"非空列(#存在)"的位置
    state : 在 lst 这一列,连通前沿所在的状态(1 / 2 / 3)
    */
    int ans=0;
    int lst=-1,state=-1;

    // 从左到右扫描所有列
    for(int i=0;i<n;i++)
    {
        // 如果这一列上下都为空,不是"关键列",直接跳过
        if(a[i]=='.'&&b[i]=='.') continue;

        // 当前列原始已有 # 的状态
        int cur=cal(a[i],b[i]);

        // 如果这是第一次遇到非空列,第一段 # 不需要补任何东西,连通块从这里开始
        if(lst==-1)
        {
            lst=i;
            state=cur;
            continue;
        }

        //从上一段非空列 lst 走到当前非空列 i,中间一共有:gap = i - lst - 1个"全空列"
        //情况 1:需要"换行"的冲突情况
        if( (cur==1 && state==2) || (cur==2 && state==1) )
        {
            ans += i - lst;
            //一旦完成了这次换行,连通块在这一段已经上下打通,对后续来说等价于 state = 3
            cur = 3;
        }
        //情况 2:不需要换行
        else
        {
            ans += i - lst - 1;
        }

        // 更新"上一段非空列"的位置和连通前沿状态
        lst = i;
        state = cur;
    }
    cout<<ans<<endl;
    return 0;
}

问题7

问题描述

小明正在改造一个生产车间的生产流水线。这个车间共有 n 台设备,构成以 11为根结点的一棵树,结点 i 有权值 wi。

其中,叶结点的权值 wi 表示每单位时间产出 wi 单位材料并送往父结点;根结点的权值 wi 表示每单位时间内能打包 wi 单位成品; 其他结点的权值 wi 表示每单位时间最多能加工 wi 单位材料并送往父结点。

由于生产线中某些结点产能不足,导致无法正常运行,即某些结点每单位时间收到的材料超过其加工能力上限。小明计划删除一些结点使所有结点都能正常运行,想知道删除后根结点每单位时间最多能打包多少单位成品。

输入格式

输入共 n+1 行。

第一行为一个正整数 n.

第二行为 n 个由空格分开的正整数 w1,w2,...,wn.

后面 n−1行,每行两个整数,表示树上的一条边连接的两个结点。

输出格式

输出共一行,一个整数,表示根结点每单位时间最多能打包的成品单位数。

样例输入

复制代码
9
9 7 3 7 1 6 2 2 7
1 2
1 3
2 4
2 5
2 6
6 7
6 8
6 9

样例输出

复制代码
8

样例说明

删掉结点 4,9 后生产线满足条件,根结点 1 每单位时间将打包 8 单位成品。

评测用例规模与约定

对于 20% 的评测用例,2≤n≤100。

对于 100% 的评测用例,2≤n≤1000, wi≤1000。

问题的关键点(最重要)

  1. 删除结点 = 删除整棵子树

    • 一旦删除某个结点,它和它的所有后代都不再向上送材料
    • 所以在父结点处,真正的决策是:
      "要不要保留某个孩子子树"
  2. 流量自底向上

    • 叶子固定产出 w[i]
    • 非叶子只是"接收 + 加工 + 向上转发",不创造材料
  3. 结点的核心约束

    • 每个结点 u

      ∑(保留孩子送来的量)≤w[u]\sum(\text{保留孩子送来的量}) \le w[u]∑(保留孩子送来的量)≤w[u]

DP 状态设计的关键点

  1. 子树向上"可输出的不是一个数,而是一个集合"

    • 因为子树内部也可以删点
    • 所以一个子树可能输出 0、2、5、7... 等不同值
  2. 定义 dp[u]

    dp[u][k]=1 表示:

    在 u 的子树中删点后,u 可以向父亲输出 k,并且 u 自身不超载

  3. dp[u] 的上界

    • k ≤ w[u]
    • 所以 dp[u] 长度只需要 w[u]+1

状态转移(树形背包)的关键点

  1. 叶子结点
    • 只能输出 w[u]
    • dp[u] = { w[u] }
  2. 非叶子结点 = 背包合并孩子
    • 初始:cur = {0}(不选任何孩子)
    • 对每个孩子 v
      • "不选 v" → 保留原 cur
      • "选 v" → 把 dp[v] 中的每个 t 加到 cur 里
    • 合并时始终保证 k + t ≤ w[u]
  3. 顺序要求
    • 必须先算孩子,再算父亲
      后序遍历

遍历与实现的关键点

  1. 无向树 → 有根树
    • DFS 时传 parent
    • parent_[u] 区分父亲和孩子
  2. 判断叶子的方法
    • 在有根树中:
      除了父亲,没有其他邻居
  3. 为什么不用 BFS
    • 因为 DP 依赖"孩子 → 父亲"的方向
    • BFS 不能保证顺序正确

答案的获取

  1. 根结点的 dp[1] 就是最终结果集合
  2. 答案 = dp[1] 中最大的 k

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int n;
vector<int> w;
vector<vector<int>> g;
vector<int> parent_;
vector<int> order; // 后序

void dfs(int u, int p) {
    parent_[u] = p;
    for (int v : g[u]) {
        if (v == p) continue;
        dfs(v, u);
    }
    order.push_back(u); // 后序:孩子先入,再入父
}

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

    cin >> n;
    w.assign(n + 1, 0);
    for (int i = 1; i <= n; ++i) cin >> w[i];

    g.assign(n + 1, {});
    for (int i = 1; i <= n - 1; ++i) {
        int x, y;
        cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }

    parent_.assign(n + 1, 0);
    order.clear();
    dfs(1, 0);

    // dp[u]:长度 w[u]+1,dp[u][k]=1 表示 u 能向上输出 k
    vector<vector<char>> dp(n + 1);

    for (int u : order) {
        // 判断 u 是否为根树意义下叶子:除了父亲外没有孩子
        bool isLeaf = true;
        for (int v : g[u]) {
            if (v != parent_[u]) { 
                isLeaf = false; break;
            }
        }

        dp[u].assign(w[u] + 1, 0);

        if (isLeaf) {
            // 叶子:如果保留它,则固定产出 w[u]
            dp[u][w[u]] = 1;
            continue;
        }

        // 非叶子:从 cur={0} 开始合并孩子
        vector<char> cur(w[u] + 1, 0);
        cur[0] = 1; // 一个孩子都不选(等价都删掉)总输出 0

        for (int v : g[u]) {
            if (v == parent_[u]) continue;

            // next 初始化为 cur:表示"不选 v 子树"(删除 v)
            vector<char> next = cur;

            // 枚举 v 能贡献的所有流量 t(dp[v][t]=1)
            // 然后做背包:如果当前能做到 k,那么也能做到 k+t
            for (int t = 0; t < (int)dp[v].size(); ++t) {
                if (!dp[v][t]) continue;
                for (int k = 0; k + t <= w[u]; ++k) {
                    if (cur[k]) next[k + t] = 1;
                }
            }

            cur.swap(next);
        }

        dp[u] = cur; // u 能输出的所有可能值集合
    }

    // 根节点答案:dp[1] 中最大的 k
    int ans = 0;
    for (int k = w[1]; k >= 0; --k) {
        if (dp[1][k]) { ans = k; break; }
    }

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

问题8

问题描述

老王计划装修房子,于是联系了一家装修公司。该公司有一套自动报价系统,只需用户提供 NN 项装修相关费用 A1,A2,...,AN,系统便会根据这些费用生成最终的报价。

然而,当老王提交数据后,他发现这套系统的运作方式并不透明:系统只会给出一个最终报价,而不会公开任何运算过程或中间步骤。

公司对此解释称,这套系统会依据某种内部算法,在每对相邻数字之间插入 +(加法)、−(减法)或 ⊕(异或)运算符,并按照特定优先级规则计算总和:异或运算优先级最高,其次是加减。但由于保密性,具体的运算符组合以及中间过程都不会对外公开。

为了验证系统报价是否合理,老王决定模拟其运作方式,尝试每种可能的运算符组合,计算出所有可能出现的总和。如果最终报价明显超出这个范围,他就有理由怀疑系统存在异常或误差。只是老王年事已高,手动计算颇为吃力,便向你求助。

现在,请你帮老王算出所有可能的总和。由于该总和可能很大,你只需提供其对 109 +7取余后的结果即可。

输入格式

第一行输入一个整数 N,表示装修相关费用的项数。

第二行输入 N 个非负整数 A1,A2,...,AN,表示各项费用。

输出格式

输出一个整数,表示所有可能的总和对 109+7109+7 取余后的结果。

样例输入

复制代码
3
0 2 5

样例输出

复制代码
11

样例说明

对于输入样例中的三个数 A=[0,2,5]A=[0,2,5],所有可能的运算符组合共有 99 种。计算结果如下:
0⊕2⊕5=70⊕2+5=70⊕2−5=−30+2⊕5=70+2+5=70+2−5=−30−2⊕5=−70−2+5=30−2−5=−7 \begin{array}{lll} 0 \oplus 2 \oplus 5 = 7 & 0 \oplus 2 + 5 = 7 & 0 \oplus 2 - 5 = -3 \\ 0 + 2 \oplus 5 = 7 & 0 + 2 + 5 = 7 & 0 + 2 - 5 = -3 \\ 0 - 2 \oplus 5 = -7 & 0 - 2 + 5 = 3 & 0 - 2 - 5 = -7 \end{array} 0⊕2⊕5=70+2⊕5=70−2⊕5=−70⊕2+5=70+2+5=70−2+5=30⊕2−5=−30+2−5=−30−2−5=−7

所有结果的总和为:7+7+(−3)+7+7+(−3)+(−7)+3+(−7)=11

11 对 109 +7取余后的值依然为 11,因此,输出结果为 11。

评测用例规模与约定

对于 30% 的评测用例,1≤N≤13,0≤Ai≤103

对于 60% 的评测用例,1≤N≤103 ,0≤Ai≤105

对于所有评测用例,1≤N≤105 ,0≤Ai≤109

问题关键(本题的突破口)

1.运算优先级的等价描述

由于 优先级最高,任意表达式都可以被拆分为:

若干段 连续异或子段 ,再用 +- 连接这些子段

例如:

复制代码
A1 ⊕ A2 + A3 ⊕ A4 - A5

等价于:

复制代码
(A1 ⊕ A2) + (A3 ⊕ A4) - (A5)
2. 只关心"第一个不是 ⊕ 的运算符"

将所有表达式,按照从左到右第一个不是 ⊕ 的运算符位置分类。

  • 若不存在 + / -,则整个表达式为

    A1⊕A2⊕⋯⊕ANA1⊕A2⊕⋯⊕ANA1⊕A2⊕⋯⊕AN

  • 否则,第一个 + / - 出现在位置 iii,表达式形如:

    (A1⊕⋯⊕Ai)±(后缀)(A1⊕⋯⊕Ai) ± (后缀)(A1⊕⋯⊕Ai)±(后缀)

设前缀异或:

Si=A1⊕A2⊕⋯⊕AiSi=A1⊕A2⊕⋯⊕AiSi=A1⊕A2⊕⋯⊕Ai

3.「+」与「-」的对称抵消效应(核心结论)

对于固定的位置 i:

  • 在第 i个位置选择 +-完全对称的
  • 后面剩余 N−i−1 个位置,每个都有 3 种选择
  • 因此:
    • + S_i 出现 3N−i−13^{N-i-1}3N−i−1次
    • - S_i 也出现 3N−i−13^{N-i-1}3N−i−1次

对所有表达式结果求和时:

(+Si)⋅3N−i−1+(−Si)⋅3N−i−1=0(+Si)⋅3^{N−i−1}+(−Si)⋅3^{N−i−1}=0(+Si)⋅3N−i−1+(−Si)⋅3N−i−1=0

符号层面抵消

但注意:
每当出现第一个 + / -,前缀异或段 Sii一定会被"结算一次" ,不论是 + 还是 -

因此,真正的结论是:

在所有表达式结果的总和中,
只有"前缀全部使用 ⊕"的异或值会产生贡献。

数学推导(总和公式)

前缀贡献

对于每个位置 i=1∼N−1

  • 若第一个 + / - 出现在位置 iii

  • 前缀异或值为 SiS_iSi

  • 该情况出现的次数为:

    2×3N−i−12×3^{N−i−1}2×3N−i−1

    +- 两种 × 后缀自由选择)

因此贡献为:

2⋅3N−i−1⋅Si2⋅3^{N−i−1}⋅Si2⋅3N−i−1⋅Si

全异或表达式

只有一种情况:

A1⊕A2⊕⋯⊕AN=SNA1⊕A2⊕⋯⊕AN=SNA1⊕A2⊕⋯⊕AN=SN

最终总和公式
Sum=∑i=1N−1(2⋅3N−i−1⋅Si)+SNmod(109+7) Sum=\sum_{i=1}^{N−1}(2⋅3^{N−i−1}⋅Si)+S_N mod (10^9+7) Sum=i=1∑N−1(2⋅3N−i−1⋅Si)+SNmod(109+7)

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
const LL MOD = 1e9 + 7;

int n;
LL a[N];
LL pow3[N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];

    // 预处理 3 的幂
    pow3[0] = 1;
    for(int i = 1; i <= n; i++)
        pow3[i] = pow3[i - 1] * 3 % MOD;

    LL ans = 0;
    LL s = 0; // 前缀异或

    for(int i = 1; i < n; i++)
    {
        s ^= a[i]; // S_i
        ans = (ans + s * 2 % MOD * pow3[n - i - 1] % MOD) % MOD;
    }

    // 全异或情况
    s ^= a[n]; // S_n
    ans = (ans + s) % MOD;

    cout << ans << endl;
    return 0;
}
相关推荐
Aevget6 小时前
MFC扩展库BCGControlBar Pro v37.2新版亮点:控件功能进一步升级
c++·mfc·界面控件
六义义6 小时前
java基础十二
java·数据结构·算法
四维碎片6 小时前
QSettings + INI 笔记
笔记·qt·算法
Tansmjs6 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
独自破碎E7 小时前
【优先级队列】主持人调度(二)
算法
weixin_445476687 小时前
leetCode每日一题——边反转的最小成本
算法·leetcode·职场和发展
打工的小王7 小时前
LeetCode Hot100(一)二分查找
算法·leetcode·职场和发展
Swift社区7 小时前
LeetCode 385 迷你语法分析器
算法·leetcode·职场和发展
sonadorje8 小时前
svd在图像处理中的应用
算法
挖矿大亨8 小时前
c++中的函数模版
java·c++·算法