[NOIP 2015 普及组] 求和 c++ 解析
这段代码的核心是通过。
[NOIP 2015 普及组] 求和
题目背景
NOIP2015 普及组 T3
题目描述
一条狭长的纸带被均匀划分出了 nnn 个格子,格子编号从 111 到 nnn。每个格子上都染了一种颜色 coloricolor_icolori 用 [1,m][1,m][1,m] 当中的一个整数表示),并且写了一个数字 numberinumber_inumberi。
| 编号 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 颜色和数字 | 5\color{blue}{5}5 | 5\color{blue}{5}5 | 3\color{red}{3}3 | 2\color{red}{2}2 | 2\color{blue}{2}2 | 2\color{red}{2}2 |
定义一种特殊的三元组:(x,y,z)(x,y,z)(x,y,z),其中 x,y,zx,y,zx,y,z 都代表纸带上格子的编号,这里的三元组要求满足以下两个条件:
-
x,y,zx,y,zx,y,z 都是整数,x<y<z,y−x=z−yx<y<z,y-x=z-yx<y<z,y−x=z−y。
-
colorx=colorzcolor_x=color_zcolorx=colorz。
满足上述条件的三元组的分数规定为 (x+z)×(numberx+numberz)(x+z) \times (number_x+number_z)(x+z)×(numberx+numberz)。整个纸带的分数规定为所有满足条件的三元组的分数的和。这个分数可能会很大,你只要输出整个纸带的分数除以 100071000710007 所得的余数即可。
输入格式
第一行是用一个空格隔开的两个正整数 nnn 和 m,nm,nm,n 表纸带上格子的个数,mmm 表纸带上颜色的种类数。
第二行有 nnn 用空格隔开的正整数,第 iii 个数字表示纸带上编号为 iii 格子上面写的数字 numberinumber_inumberi。
第三行有 nnn 用空格隔开的正整数,第 iii 数字表示纸带上编号为 iii 格子染的颜色 coloricolor_icolori。
输出格式
一个整数,表示所求的纸带分数除以 100071000710007 所得的余数。
输入输出样例 #1
输入 #1
6 2
5 5 3 2 2 2
2 2 1 1 2 1
输出 #1
82
输入输出样例 #2
输入 #2
15 4
5 10 8 2 2 2 9 9 7 7 5 6 4 2 4
2 2 3 3 4 3 3 2 4 4 4 4 1 1 1
输出 #2
1388
说明/提示
样例 1 解释
纸带如题目描述中的图所示。
所有满足条件的三元组为:(1,3,5),(4,5,6)(1, 3, 5), (4, 5, 6)(1,3,5),(4,5,6)。
所以纸带的分数为 (1+5)×(5+2)+(4+6)×(2+2)=42+40=82(1 + 5) \times (5 + 2) + (4 + 6) \times (2 + 2) = 42 + 40 = 82(1+5)×(5+2)+(4+6)×(2+2)=42+40=82。
对于第 111 组至第 222 组数据, 1≤n≤100,1≤m≤51 ≤ n ≤ 100, 1 ≤ m ≤ 51≤n≤100,1≤m≤5;
对于第 333 组至第 444 组数据,1≤n≤3000,1≤m≤1001 ≤ n ≤ 3000, 1 ≤ m ≤ 1001≤n≤3000,1≤m≤100;
对于第 555 组至第 $ 6 $ 组数据,1≤n≤100000,1≤m≤1000001 ≤ n ≤ 100000, 1 ≤ m ≤ 1000001≤n≤100000,1≤m≤100000,且不存在出现次数超过 $ 20 $ 的颜色;
对于全部 101010 组数据,1≤n≤100000,1≤m≤100000,1≤colori≤m,1≤numberi≤1000001 ≤ n ≤ 100000, 1 ≤ m ≤ 100000, 1 ≤ color_i ≤ m,1≤number_i≤1000001≤n≤100000,1≤m≤100000,1≤colori≤m,1≤numberi≤100000。
核心思路分析
如果直接枚举所有可能的 (i, j) 二元组,时间复杂度为 O(n²),当 n 较大时会超时。因此需要优化计算方式:
通过拆分贡献表达式,用前缀累计的方式记录中间结果,将时间复杂度降至 O(n)。
贡献表达式拆分
对于符合条件的二元组 (i, j)(i < j),其贡献为:
(i + j) * (num[i] + num[j])
展开后可拆分为 4 项:
= i*num[i] + i*num[j] + j*num[i] + j*num[j]
当处理到 j 时,所有满足条件的 i(i < j、同色、同奇偶)已经被处理过。因此可以累计这些 i 的信息,直接计算它们对 j 的贡献,而无需重复枚举。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, m, num[114514], color[114514];
int a1[114514][2], a2[114514][2],a3[114514][2],a4[114514][2];
const int p = 10007;
// a1 x * numx // a2 x
// a3 numx // a4 1
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i<= n; i++)
scanf("%d", &num[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &color[i]);
int ans = 0;
for(int i = 1;i <= n;i++){
int y = i % 2;
ans += a1[color[i]][y]; ans %= p;
ans += a2[color[i]][y] * num[i]; ans %= p;
ans += a3[color[i]][y] * i; ans %= p;
ans += a4[color[i]][y] * i % p * num[i] % p; ans %= p;
a1[color[i]][y] += 1LL * i * num[i] % p;
a1[color[i]][y] %= p;
a2[color[i]][y] += i; a2[color[i]][y] %= p;
a3[color[i]][y] += num[i]; a3[color[i]][y] %= p;
++a4[color[i]][y]; a4[color[i]][y] %= p;
}
printf("%d",ans);
return 0;
}
代码变量与数组解析
代码中定义了 4 个核心数组,用于累计「符合条件的 i」的信息(按颜色和奇偶性分组):
a1[c][y]:累计颜色为c、奇偶性为y的所有i的i * num[i]之和;a2[c][y]:累计颜色为c、奇偶性为y的所有i的i之和;a3[c][y]:累计颜色为c、奇偶性为y的所有i的num[i]之和;a4[c][y]:累计颜色为c、奇偶性为y的所有i的数量(即满足条件的i的个数)。
其中 c 表示颜色,y 表示奇偶性(0 为偶,1 为奇)。
代码执行流程详解
-
输入数据:读入
n(元素个数)、m(颜色数,代码中未直接使用),以及数组num(数值)和color(颜色)。 -
初始化答案:
ans = 0,用于累计所有二元组的贡献和。 -
遍历每个元素
j(从 1 到n):-
计算
j的奇偶性y = j % 2(0为偶,1为奇)。 -
取出
j的颜色c = color[j]。 -
累加当前
j的贡献:
对于所有已处理的i < j(且color[i] = c、i%2 = y),它们对j的贡献是拆分后的 4 项之和:ans += a1[c][y]; // 对应 i*num[i] 的和 ans += a2[c][y] * num[j]; // 对应 i*num[j] 的和(num[j] 是常数,乘以 i 的和) ans += a3[c][y] * j; // 对应 j*num[i] 的和(j 是常数,乘以 num[i] 的和) ans += a4[c][y] * j * num[j]; // 对应 j*num[j] 的和(j*num[j] 是常数,乘以 i 的个数)每步累加后都对
10007取模,防止溢出。 -
更新累计数组:
将当前j的信息加入对应分组(颜色c、奇偶性y),供后续元素(k > j)计算贡献:a1[c][y] += (j * num[j]) % p; // 累计 i*num[i](当前 j 作为未来的 i) a2[c][y] += j % p; // 累计 i(当前 j 作为未来的 i) a3[c][y] += num[j] % p; // 累计 num[i](当前 j 作为未来的 i) a4[c][y] += 1; // 累计 i 的数量(当前 j 作为未来的 i)同样每步对
10007取模。
-
-
输出结果:打印最终的
ans % 10007。
总结
这段代码的核心是通过拆分贡献表达式和分组累计前缀信息,将原本 O(n²) 的枚举优化为 O(n) 的线性遍历,高效求解了符合条件的二元组贡献和。关键在于用 4 个数组记录不同维度的累计值,避免重复计算。
更多推荐
所有评论(0)