16届蓝桥杯B组 C++题解
问题1(填空题)
问题描述
小明初始在二维平面的原点 (0,0)(0,0),他想前往坐标 (233,666)。
在移动过程中,他只能采用以下两种移动方式,并且这两种移动方式可以交替、不限次数地使用:
- 水平向右移动,即沿着 xx 轴正方向移动一定的距离。
- 沿着一个圆心在原点 (0,0)(0,0)、以他当前位置到原点的距离为半径的圆的圆周移动,移动方向不限(即顺时针或逆时针移动不限)。
在这种条件下,他到达目的地最少移动多少单位距离?
只需输出答案四舍五入到整数的结果。
问题关键
-
水平移动仅改变横坐标,而且开头第一次移动必须是水平移动;
-
最优情况下,到达目标点的最后一段移动绝对是弧(反证法易证);
-
最后那段弧,必然属于以(0,0)为原点,以原点到目标点的直线距离为半径的圆;
-
总结1与2最优路径必然是两段式(先水平移动,再圆周移动);
-
多段交替移动(如 "水平→圆周→水平→圆周")不会产生更短的路径。
-
数学上可以证明:任何多段路径的总距离,都大于或等于 "先水平移动到目标点所在圆的 x 轴交点,再沿圆弧到目标点" 的两段式距离。
-
本质原因:额外的水平移动会增加径向距离,额外的圆周移动会增加角度距离,两者都会让总距离变大。
-
-
最后一段弧的圆心角等于目标点的极角 θ ;
数值计算
- 目标点到原点的距离: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取余后的结果即可。
问题关键
- i可以等于j;
- 2025必须放在A2025上,否则Ai* Aj<=2025+ij不成立,例如:A2024=2025时,2025 * 2025 > 20242024+2025;
- 同2可证:i>=1013时,Ai = i;
- i < 1013时,Ai=i+1 , i , i-1 , ...... , 1 ;从前往后考虑,A1 有 1、2 两种选法,A2 在 A1 选过之后,还有 2 种选法;A3在前两个选完之后,还有 2 种选法;...;A1012在前面都选完之后,只剩下 2 种选法 。因此,总共的方案数为 21012 ;
数值计算
-
21012 % ( 109 +7 )
cppfor(int i = 1; i <= 1012; i++){ result = result * 2 % mod; }
结果
781448427
问题3
问题描述
定义一种特殊的整数序列:这种序列由连续递增的整数组成,并满足以下条件:
- 序列长度至少为 3。
- 序列中的数字是连续递增的整数(即相邻元素之差为 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 。
问题关键
-
排序消除绝对值,方便后续操作;
-
最优子数组 具有连续性 ;
假设原数组已经排序为: 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 最小。
-
-
滑动窗口高效求解 ;
-
由于艺术价值是正整数,平方后数值可能很大,必须用
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。
问题关键
- 状态编码 :用
cal函数将每列的#分布编码为 3 种状态(1:上 #下.;2:上。下 #;3:上下 #) ; - 扫描列 :从左到右遍历所有列,仅关注 "非空列"(含
#的列) ; - 连通非空列 :记录上一个非空列的位置与状态,通过 "当前列与上一非空列的状态关系" 计算中间全空列需新增的
#数量:- 若状态是1 和 2 互斥 (上一列为上 #、当前为下 #,或反之):需 "打通上下",新增
i-lst个#(等价于中间全空列数 + 1),并将当前状态更新为 3(上下连通)。 - 若状态兼容(如均为 1、均为 2、含 3):仅需铺中间全空列的对应行,新增
i-lst-1个#。
- 若状态是1 和 2 互斥 (上一列为上 #、当前为下 #,或反之):需 "打通上下",新增
代码
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。
问题的关键点(最重要)
-
删除结点 = 删除整棵子树
- 一旦删除某个结点,它和它的所有后代都不再向上送材料
- 所以在父结点处,真正的决策是:
"要不要保留某个孩子子树"
-
流量自底向上
- 叶子固定产出
w[i] - 非叶子只是"接收 + 加工 + 向上转发",不创造材料
- 叶子固定产出
-
结点的核心约束
-
每个结点
u:∑(保留孩子送来的量)≤w[u]\sum(\text{保留孩子送来的量}) \le w[u]∑(保留孩子送来的量)≤w[u]
-
DP 状态设计的关键点
-
子树向上"可输出的不是一个数,而是一个集合"
- 因为子树内部也可以删点
- 所以一个子树可能输出 0、2、5、7... 等不同值
-
定义 dp[u]
dp[u][k]=1表示:在 u 的子树中删点后,u 可以向父亲输出 k,并且 u 自身不超载
-
dp[u] 的上界
k ≤ w[u]- 所以 dp[u] 长度只需要
w[u]+1
状态转移(树形背包)的关键点
- 叶子结点
- 只能输出
w[u] dp[u] = { w[u] }
- 只能输出
- 非叶子结点 = 背包合并孩子
- 初始:
cur = {0}(不选任何孩子) - 对每个孩子
v:- "不选 v" → 保留原 cur
- "选 v" → 把
dp[v]中的每个t加到 cur 里
- 合并时始终保证
k + t ≤ w[u]
- 初始:
- 顺序要求
- 必须先算孩子,再算父亲
后序遍历
- 必须先算孩子,再算父亲
遍历与实现的关键点
- 无向树 → 有根树
- DFS 时传
parent - 用
parent_[u]区分父亲和孩子
- DFS 时传
- 判断叶子的方法
- 在有根树中:
除了父亲,没有其他邻居
- 在有根树中:
- 为什么不用 BFS
- 因为 DP 依赖"孩子 → 父亲"的方向
- BFS 不能保证顺序正确
答案的获取
- 根结点的 dp[1] 就是最终结果集合
- 答案 = 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;
}