蓝桥杯 2022年省赛真题
C/C++ 大学C组
省流,十道签到题。
试题 A: 排列字母
本题总分: 5 5 5 分
【问题描述】
小蓝要把一个字符串中的字母按其在字母表中的顺序排列。
例如, L A N Q I A O \mathrm{LANQIAO} LANQIAO 排列后为 A A I L N O Q \mathrm{AAILNOQ} AAILNOQ。
又如, G O O D G O O D S T U D Y D A Y D A Y U P \mathrm{GOODGOODSTUDYDAYDAYUP} GOODGOODSTUDYDAYDAYUP 排列后为 A A D D D D D G G O O O O P S T U U Y Y Y \mathrm{AADDDDDGGOOOOPSTUUYYY} AADDDDDGGOOOOPSTUUYYY。
请问对于以下字符串,排列之后字符串是什么?
W H E R E T H E R E I S A W I L L T H E R E I S A W A Y \mathrm{WHERETHEREISAWILLTHEREISAWAY} WHERETHEREISAWILLTHEREISAWAY
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个由大写字母组成的字符串,在提交答案时只填写这个字符串,填写多余的内容将无法得分。
AAAEEEEEEHHHIIILLRRRSSTTWWWY
#include <stdio.h>
char *str = "WHERETHEREISAWILLTHEREISAWAY";
int total[128];
int main() {
while (*str) ++total[*str++];
for (char i = 'A'; i <= 'Z'; ++i)
while (total[i]--) putchar(i);
}
指针乱飞就完事了。
试题 B: 特殊时间
本题总分: 5 5 5 分
【问题描述】
2022 2022 2022 年 2 2 2 月 22 22 22 日 22 22 22: 20 20 20 是一个很有意义的时间,年份为 2022 2022 2022,由 3 3 3 个 2 2 2 和 1 1 1 个 0 0 0 组成,如果将月和日写成 4 4 4 位,为 0222 0222 0222,也是由 3 3 3 个 2 2 2 和 1 1 1 个 0 0 0 组成,如果将时间中的时和分写成 4 4 4 位,还是由 3 3 3 个 2 2 2 和 1 1 1 个 0 0 0 组成。
小蓝对这样的时间很感兴趣,他还找到了其它类似的例子,比如 111 111 111 年 10 10 10 月 11 11 11 日 01 01 01: 11 11 11, 2202 2202 2202 年 2 2 2 月 22 22 22 日 22 22 22: 02 02 02 等等。
请问,总共有多少个时间是这种年份写成 4 4 4 位、月日写成 4 4 4 位、时间写成 4 4 4 位后由 3 3 3 个一种数字和 1 1 1 个另一种数字组成。注意 1111 1111 1111 年 11 11 11 月 11 11 11 日 11 11 11: 11 11 11 不算,因为它里面没有两种数字。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
212
在 3 3 3 个相同的个位数中插入 1 1 1 个个位数,显然可以组成 4 4 4 个不同的数字(不一定是 4 4 4 位数),于是我们可以另一个合法的 月日时分 与 4 4 4 个不同的年份组成映射关系,只要统计出合法的 日月时分 个数,将其乘上一个 4 4 4,答案就被计算出来了。
#include <stdio.h>
int buff[10], ans = 0;
int days[]{0, 31, 29, 31, 30 ,31, 30, 31, 31, 30 ,31, 30, 31};
int main() {
for (int MM = 1; MM <= 12; ++MM)
for (int dd = 1; dd <= days[MM]; ++dd)
for (int HH = 0; HH < 24; ++HH)
for (int mm = 0; mm < 60; ++mm) {
for (int i = 0; i < 10; ++i) buff[i] = 0;
++buff[MM / 10]; ++buff[MM % 10];
++buff[dd / 10]; ++buff[dd % 10];
bool flag1 = 1, flag2 = 1;
for (int i = 0; i < 10; ++i)
if (buff[i] == 3) flag1 = 0;
else if (buff[i] == 1) flag2 = 0;
if (flag1 || flag2) continue;
--buff[HH / 10]; --buff[HH % 10];
--buff[mm / 10]; --buff[mm % 10];
for (int i = 0; i < 10; ++i)
if (buff[i] != 0) flag1 = 1;
if (!flag1) ++ans;
}
printf("%d", ans << 2);
}
试题 C: 纸张尺寸
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 10 10 10 分
【问题描述】
在 I S O \mathrm{ISO} ISO 国际标准中定义了 A 0 \mathrm A0 A0 纸张的大小为 1189 m m × 841 m m 1189\mathrm{mm} × 841\mathrm{mm} 1189mm×841mm,将 A 0 \mathrm A0 A0 纸沿长边对折后为 A 1 \mathrm A1 A1 纸,大小为 841 m m × 594 m m 841\mathrm{mm} × 594\mathrm{mm} 841mm×594mm,在对折的过程中长度直接取下整(实际裁剪时可能有损耗)。将 A 1 \mathrm A1 A1 纸沿长边对折后为 A 2 \mathrm A2 A2 纸,依此类推。
输入纸张的名称,请输出纸张的大小。
【输入格式】
输入一行包含一个字符串表示纸张的名称,该名称一定是 A 0 \mathrm A0 A0、 A 1 \mathrm A1 A1、 A 2 \mathrm A2 A2、 A 3 \mathrm A3 A3、 A 4 \mathrm A4 A4、 A 5 \mathrm A5 A5、 A 6 \mathrm A6 A6、 A 7 \mathrm A7 A7、 A 8 \mathrm A8 A8、 A 9 \mathrm A9 A9 之一。
【输出格式】
输出两行,每行包含一个整数,依次表示长边和短边的长度。
【样例输入 1】
A0
【样例输出 1】
1189
841
【样例输入 2】
A1
【样例输出 2】
841
594
签到,
格式化输入,行!
#include <stdio.h>
int n, length = 1189, wide = 841, temp;
int main() {
scanf("A%d", &n);
while (n--) {
temp = length;
length = wide;
wide = temp / 2;
}
printf("%d\n%d", length, wide);
}
试题 D: 求和
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 10 10 10 分
【问题描述】
给定 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots , a_n a1,a2,⋯,an,求它们两两相乘再相加的和,即 S = a 1 ⋅ a 2 + a 1 ⋅ a 3 + ⋯ + a 1 ⋅ a n + a 2 ⋅ a 3 + ⋯ + a n − 2 ⋅ a n − 1 + a n − 2 ⋅ a n + a n − 1 ⋅ a n 。 S = a_1\cdot a_2 + a_1\cdot a_3 + \cdots + a_1\cdot a_n + a_2\cdot a_3 +\cdots + a_{n−2}\cdot a_{n−1} + a_{n−2}\cdot a_n + a_{n−1}\cdot a_n。 S=a1⋅a2+a1⋅a3+⋯+a1⋅an+a2⋅a3+⋯+an−2⋅an−1+an−2⋅an+an−1⋅an。
【输入格式】
输入的第一行包含一个整数 n n n 。
第二行包含 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1, a_2,\cdots,a_n a1,a2,⋯,an。
【输出格式】
输出一个整数 S S S,表示所求的和。请使用合适的数据类型进行运算。
【样例输入】
4
1 3 6 9
【样例输出】
117
【评测用例规模与约定】
对于
30
%
30\%
30% 的数据,
1
≤
n
≤
1000
,
1
≤
a
i
≤
100
1 ≤ n ≤ 1000,1 ≤ a_i ≤ 100
1≤n≤1000,1≤ai≤100。
对于所有评测用例,
1
≤
n
≤
200000
,
1
≤
a
i
≤
1000
1 ≤ n ≤ 200000,1 ≤ a_i ≤ 1000
1≤n≤200000,1≤ai≤1000。
公式递推
将 S S S 中包含 a 1 a_1 a1 的项整理出来,有:
S = a 1 ⋅ ( a 2 + a 3 + ⋯ + a n ) + a 2 ⋅ a 3 + ⋯ + a n − 2 ⋅ a n − 1 + a n − 2 ⋅ a n + a n − 1 ⋅ a n S=a_1\cdot(a_2+a_3+\cdots+a_n)+ a_2\cdot a_3 +\cdots + a_{n−2}\cdot a_{n−1} + a_{n−2}\cdot a_n + a_{n−1}\cdot a_n S=a1⋅(a2+a3+⋯+an)+a2⋅a3+⋯+an−2⋅an−1+an−2⋅an+an−1⋅an
同样的将余项中包含 a 2 a_2 a2、 a 3 a_3 a3、 ⋯ \cdots ⋯、 a n a_n an 的项整理出来:
S = a 1 ⋅ ( a 2 + a 3 + ⋯ + a n ) + a 2 ⋅ ( a 3 + a 4 + ⋯ + a n ) + ⋯ + a n − 1 ⋅ a n = a 1 ⋅ ∑ i = 2 n a i + a 2 ⋅ ∑ i = 3 n a i + ⋯ + a n − 1 ⋅ ∑ i = n n a i \begin{aligned}S&=a_1\cdot(a_2+a_3+\cdots+a_n)+ a_2\cdot(a_3+a_4+\cdots+a_n)+\cdots+a_{n-1}\cdot a_n\\&=a_1\cdot\sum_{i=2}^na_i+a_2\cdot \sum_{i=3}^na_i+\cdots+a_{n-1}\cdot\sum_{i=n}^{n}a_i\end{aligned} S=a1⋅(a2+a3+⋯+an)+a2⋅(a3+a4+⋯+an)+⋯+an−1⋅an=a1⋅i=2∑nai+a2⋅i=3∑nai+⋯+an−1⋅i=n∑nai
也就 S S S 等于每个元素乘以右边所有元素的和的和,考虑到实现计算的代码量,我们将 S S S 的项重排,重新整理出:
S = a 1 ⋅ ∑ i = 1 0 a i + a 2 ⋅ ∑ i = 1 1 a i + ⋯ + a n ⋅ ∑ i = 1 n − 1 a i S=a_1\cdot\displaystyle\sum_{i=1}^{0}a_i+a_2\cdot \sum_{i=1}^{1}a_i+\cdots+a_{n}\cdot\sum_{i=1}^{n-1}a_i S=a1⋅i=1∑0ai+a2⋅i=1∑1ai+⋯+an⋅i=1∑n−1ai
#include <stdio.h>
long long n, a, sum, ans;
int main() {
scanf("%d", &n);
while (n--)
scanf("%d", &a), ans += a * sum, sum += a;
printf("%lld", ans);
}
签到 × 2 ×\ 2 × 2。
试题 E: 数位排序
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 15 15 15 分
【问题描述】
小蓝对一个数的数位之和很感兴趣,今天他要按照数位之和给数排序。当两个数各个数位之和不同时,将数位和较小的排在前面,当数位之和相等时,将数值小的排在前面。
例如, 2022 2022 2022 排在 409 409 409 前面,因为 2022 2022 2022 的数位之和是 6 6 6,小于 409 409 409 的数位之和 13 13 13。
又如, 6 6 6 排在 2022 2022 2022 前面,因为它们的数位之和相同,而 6 6 6 小于 2022 2022 2022。
给定正整数 n , m n,m n,m,请问对 1 1 1 到 n n n 采用这种方法排序时,排在第 m m m 个的元素是多少?
【输入格式】
输入第一行包含一个正整数 n n n。
第二行包含一个正整数 m m m。
【输出格式】
输出一行包含一个整数,表示答案。
【样例输入】
13
5
【样例输出】
3
【样例说明】
1
1
1 到
13
13
13 的排序为
:
1
,
10
,
2
,
11
,
3
,
12
,
4
,
13
,
5
,
6
,
7
,
8
,
9
:1, 10, 2, 11, 3, 12, 4, 13, 5, 6, 7, 8, 9
:1,10,2,11,3,12,4,13,5,6,7,8,9。第
5
5
5 个数为
3
3
3。
【评测用例规模与约定】
对于
30
%
30\%
30% 的评测用例,
1
≤
m
≤
n
≤
300
1 ≤ m ≤ n ≤ 300
1≤m≤n≤300。
对于
50
%
50\%
50% 的评测用例,
1
≤
m
≤
n
≤
1000
1 ≤ m ≤ n ≤ 1000
1≤m≤n≤1000。
对于所有评测用例,
1
≤
m
≤
n
≤
1
0
6
1 ≤ m ≤ n ≤ 10^6
1≤m≤n≤106。
签到 × 3 ×\ 3 × 3。
#include <stdio.h>
#include <algorithm>
struct number {
int v, w = 0;
number(int n = 0) {
v = n;
while (n)
w += n % 10, n /= 10;
}
};
bool cmp(const number &n1, const number &n2) { return n1.w == n2.w ? n1.v < n2.v : n1.w < n2.w; }
number ns[1000001];
int n, m;
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) ns[i] = number(i);
std::sort(ns + 1, ns + n + 1, cmp);
printf("%d", ns[m].v);
}
试题 F: 选数异或
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 15 15 15 分
【问题描述】
给定一个长度为 n n n 的数列 A 1 , A 2 , ⋯ , A n A_1, A_2, \cdots , A_n A1,A2,⋯,An 和一个非负整数 x x x,给定 m m m 次查询, 每次询问能否从某个区间 [ l , r ] [l,r] [l,r] 中选择两个数使得他们的异或等于 x x x。
【输入格式】
输入的第一行包含三个整数 n , m , x n, m, x n,m,x。
第二行包含 n n n 个整数 A 1 , A 2 , ⋯ , A n A_1, A_2,\cdots, A_n A1,A2,⋯,An。
接下来 m m m 行,每行包含两个整数 l i , r i l_i,r_i li,ri 表示询问区间 [ l i , r i ] [l_i,r_i] [li,ri]。
【输出格式】
对于每个询问, 如果该区间内存在两个数的异或为 x x x 则输出 y e s \mathrm{yes} yes, 否则输出 n o \mathrm{no} no。
【样例输入】
4 4 1
1 2 3 4
1 4
1 2
2 3
3 3
【样例输出】
yes
no
yes
no
【样例说明】
显然整个数列中只有
2
,
3
2, 3
2,3 的异或为
1
1
1。
【评测用例规模与约定】
对于
20
%
20\%
20% 的评测用例,
1
≤
n
,
m
≤
100
1 ≤ n, m ≤ 100
1≤n,m≤100;
对于
40
%
40\%
40% 的评测用例,
1
≤
n
,
m
≤
1000
1 ≤ n, m ≤ 1000
1≤n,m≤1000;
对于所有评测用例,
1
≤
n
,
m
≤
100000
,
0
≤
x
<
2
20
,
1
≤
l
i
≤
r
i
≤
n
,
0
≤
A
i
<
2
20
1 ≤ n, m ≤ 100000 ,0 ≤ x < 2^{20} ,1 ≤ l_i ≤ r_i ≤ n ,0 ≤ A_i < 2^{20}
1≤n,m≤100000,0≤x<220,1≤li≤ri≤n,0≤Ai<220。
动态规划
处理出 f r f_r fr,其意义为 [ f r , r ] [f_r,r] [fr,r] 中存在一对 k k k、 g g g , f r ≤ k ≤ g ≤ r f_r \leq k \leq g \leq r fr≤k≤g≤r 使得 A k ⊕ A g = x A_k \oplus A_g =x Ak⊕Ag=x 且 f r f_r fr 最大,若不存在则令 f r = 0 f_r = 0 fr=0。
对于每一次询问 [ l i , r i ] [l_i,r_i] [li,ri],我们只需判断 l i li li 和 f r i f_{r_i} fri 的大小关系就能确定 [ l i , r i ] [l_i,r_i] [li,ri] 之间是否存在两个数,它们的异或等于 x x x。
现在考虑转移,对于每一个 r r r,我们判断下标大于 r r r 的元素中是否存在 A r ⊕ x A_r \oplus x Ar⊕x,其中最靠 r r r 的是 f r f_r fr 的一个候选值,同时我们还要考虑 [ f r − 1 , r − 1 ] [f_{r-1},r-1] [fr−1,r−1] 中是否有更优的方案,最终有状态转移方程: f r = max { f r − 1 , max { i ∣ A i = A r ⊕ x } } f_r=\max\{f_{r-1},\max\{i|A_i=A_r\oplus x\}\} fr=max{fr−1,max{i∣Ai=Ar⊕x}}
#include <stdio.h>
int n, m, x, l, r, map[1 << 20], f[100010];
int max(int a, int b) { return a > b ? a : b; }
int main() {
scanf("%d %d %d", &n, &m, &x);
for (int r = 1; r <= n; ++r)
scanf("%d", &l), f[r] = max(f[r - 1], map[l ^ x]), map[l] = r;
while (m--)
scanf("%d %d", &l, &r), printf("%s\n", f[r] >= l ? "yes" : "no");
}
不太对,感觉我在乱压行。
签到 × 4 ×\ 4 × 4。
试题 G: 消除游戏
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 20 20 20 分
【问题描述】
在一个字符串 S S S 中,如果 S i = S i − 1 S_i = S_{i−1} Si=Si−1 且 S i ≠ S i + 1 S_i\neq S_{i+1} Si=Si+1,则称 S i S_i Si 和 S i + 1 S_{i+1} Si+1 为边缘字符。如果 S i ≠ S i − 1 S_i\neq S_{i−1} Si=Si−1 且 S i = S i + 1 S_i = S_{i+1} Si=Si+1,则 S i − 1 S_{i−1} Si−1 和 S i S_i Si 也称为边缘字符。其它的字符都不是边缘字符。
对于一个给定的串 S S S,一次操作可以一次性删除该串中的所有边缘字符 ( ( (操作后可能产生新的边缘字符 ) ) )。
请问经过 2 64 2^{64} 264 次操作后,字符串 S S S 变成了怎样的字符串,如果结果为空则输出 E M P T Y \mathrm{EMPTY} EMPTY。
【输入格式】
输入一行包含一个字符串 S S S。
【输出格式】
输出一行包含一个字符串表示答案,如果结果为空则输出 E M P T Y \mathrm{EMPTY} EMPTY。
【样例输入 1】
edda
【样例输出 1】
EMPTY
【样例输入 2】
sdfhhhhcvhhxcxnnnnshh
【样例输出 2】
s
【评测用例规模与约定】
对于
25
%
25\%
25% 的评测用例,
∣
S
∣
≤
1
0
3
|S | ≤ 10^3
∣S∣≤103 ,其中
∣
S
∣
|S|
∣S∣ 表示
S
S
S 的长度;
对于
50
%
50\%
50% 的评测用例,
∣
S
∣
≤
1
0
4
|S| ≤ 10^4
∣S∣≤104;
对于
75
%
75\%
75% 的评测用例,
∣
S
∣
≤
1
0
5
|S| ≤ 10^5
∣S∣≤105 ;
对于所有评测用例,
∣
S
∣
≤
1
0
6
|S| ≤ 10^6
∣S∣≤106,
S
S
S 中仅含小写字母。
签到 × 5 ×\ 5 × 5。
如果我们将连续相同的字符划分在一块同时操作,
考虑最差情况,一、所有字符都相同,那么单次操作次数为 1 1 1,操作完 ∣ S ∣ 2 \frac{|S|}2 2∣S∣ 次后字符串不再变化;二、字符全部被划分长度为 2 2 2 的连续块,单次操作次数为 ∣ S ∣ 2 \frac{|S|}2 2∣S∣,操作完 1 1 1 次后字符串不再变化;
不等式估一下最大值,看似是暴力解,实则复杂度仅为 O ( n ) O(n) O(n)
#include <stdio.h>
const int N = 1000010;
struct string {
int length;
char c;
} str[N], buf[N];
bool dec[N], les = 1;
int n = 1, m, c;
int main() {
while (c = getchar(), c >= 'a' && c <= 'z') str[n++] = {1, c};
while (les) {
m = 1, les = 0;
for (int i = 1, j; i < n; i = j) {
for (j = i + 1; str[i].c == str[j].c && j < n; ++j)
str[i].length += str[j].length;
dec[m] = str[i].length > 1;
buf[m++] = str[i];
}
n = 1, dec[m] = 0;
for (int i = 1; i < m; ++i) {
if (dec[i]) {
if (i > 1 && i < m - 1) buf[i].length -= 2, les = 1;
else if (m > 2) --buf[i].length, les = 1;
} else if (dec[i - 1] || dec[i + 1]) --buf[i].length, les = 1;
if (buf[i].length > 0) str[n++] = buf[i];
}
}
if (n == 1) printf("EMPTY");
else
for (int i = 1; i < n; ++i)
while (str[i].length--) putchar(str[i].c);
}
上例程序实际复杂度在 O ( n 2 ) O(n^2) O(n2),但可以通过 dotcpp 上全部用例,下面用双端链表实现 O ( n ) O(n) O(n) 写法:
#include <stdio.h>
const int N = 1000010;
int buf[N], prev[N], next[N], active[N], length[N] = {-1};
bool marked[N];
char c[N];
void remove(int idx) { if (length[idx] != -1) next[prev[idx]] = next[idx], prev[next[idx]] = prev[idx], length[idx] = -1; }
void merge(int idx) {
if (length[idx] != -1) {
while (1)
if (length[prev[idx]] == 0) remove(prev[idx]);
else if (c[prev[idx]] == c[idx]) length[idx] += length[prev[idx]], remove(prev[idx]);
else break;
while (1)
if (length[next[idx]] == 0) remove(next[idx]);
else if (c[next[idx]] == c[idx]) length[idx] += length[next[idx]], remove(next[idx]);
else break;
}
}
int n, m, t, les = 1;
int main() {
while (t = getchar(), t >= 'a' && t <= 'z')
if (t == c[m]) ++length[m];
else {
if (length[m] > 1) active[++n] = m;
next[m] = m + 1, ++m, c[m] = t, length[m] = 1, prev[m] = m - 1;
}
if (length[m] > 1) active[++n] = m;
while (les) {
les = m = 0;
for (int i = 1; i <= n; ++i) {
if (prev[active[i]]) --length[active[i]], les = 1;
if (next[active[i]]) --length[active[i]], les = 1;
if (active[i - 1] != prev[active[i]] && length[prev[active[i]]]) --length[prev[active[i]]];
if (active[i + 1] != next[active[i]] && length[next[active[i]]]) --length[next[active[i]]];
}
for (int i = 1; i <= n; ++i) {
if (length[prev[active[i]]] == 0) remove(prev[active[i]]);
if (length[next[active[i]]] == 0) remove(next[active[i]]);
merge(active[i]);
merge(prev[active[i]]);
merge(next[active[i]]);
if (length[prev[active[i]]] > 1 && !marked[prev[active[i]]]) buf[++m] = prev[active[i]], marked[prev[active[i]]] = 1;
if (length[active[i]] > 1 && !marked[active[i]]) buf[++m] = active[i], marked[active[i]] = 1;
if (length[next[active[i]]] > 1 && !marked[next[active[i]]]) buf[++m] = next[active[i]], marked[next[active[i]]] = 1;
if (!length[active[i]]) remove(active[i]);
}
n = m, active[n + 1] = 0;
for (int i = 1; i <= m; ++i) active[i] = buf[i], marked[buf[i]] = 0;
// n = m, buf[n + 1] = 0;
// for (int i = 1; i <= m; ++i) marked[buf[i]] = 0;
// std::swap(buf, active);
}
if (next[0])
for (int i = next[0]; i; i = next[i])
while (length[i]--) putchar(c[i]);
else printf("EMPTY");
}
试题 H: 重新排序
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 20 20 20 分
【问题描述】
给定一个数组 A A A 和一些查询 L i , R i L_i, R_i Li,Ri,求数组中第 L i L_i Li 至第 R i R_i Ri 个元素之和。
小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?
【输入格式】
输入第一行包含一个整数 n n n。
第二行包含 n n n 个整数 A 1 , A 2 , ⋯ , A n A_1, A_2,\cdots, A_n A1,A2,⋯,An,相邻两个整数之间用一个空格分隔。
第三行包含一个整数 m m m 表示查询的数目。
接下来 m m m 行,每行包含两个整数 L i 、 R i L_i、R_i Li、Ri ,相邻两个整数之间用一个空格分隔。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
5
1 2 3 4 5
2
1 3
2 5
【样例输出】
4
【样例说明】
原来的和为
6
+
14
=
20
6 + 14 = 20
6+14=20,重新排列为
(
1
,
4
,
5
,
2
,
3
)
(1, 4, 5, 2, 3)
(1,4,5,2,3) 后和为
10
+
14
=
24
10 + 14 = 24
10+14=24,增加了
4
4
4。
【评测用例规模与约定】
对于
30
%
30\%
30% 的评测用例,
n
,
m
≤
50
n, m ≤ 50
n,m≤50;
对于
50
%
50\%
50% 的评测用例,
n
,
m
≤
500
n, m ≤ 500
n,m≤500;
对于
70
%
70\%
70% 的评测用例,
n
,
m
≤
5000
n, m ≤ 5000
n,m≤5000;
对于所有评测用例,
1
≤
n
,
m
≤
1
0
5
,
1
≤
A
i
≤
1
0
6
,
1
≤
L
i
≤
R
i
≤
1
0
6
1 ≤ n, m ≤ 10^5,1 ≤ A_i ≤ 10^6,1 ≤ L_i ≤ R_i ≤ 10^6
1≤n,m≤105,1≤Ai≤106,1≤Li≤Ri≤106。
差分
签到 × 6 ×\ 6 × 6。
差分统计单点被覆盖的次数,然后把大的元素尽可能往覆盖次数多的点塞就行了。
#include <stdio.h>
#include <algorithm>
int A[100010], B[100010], n, m, l, r;
long long ans;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &A[i]);
scanf("%d", &m);
while (scanf("%d %d", &l, &r), m--) ++B[l], --B[r + 1];
for (int i = 1; i <= n; ++i)
B[i] += B[i - 1], ans -= (long long)A[i] * B[i];
std::sort(A + 1, A + n + 1);
std::sort(B + 1, B + n + 1);
for (int i = 1; i <= n; ++i)
ans += (long long)A[i] * B[i];
printf("%lld", ans);
}
试题 I: 技能升级
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 25 25 25 分
【问题描述】
小蓝最近正在玩一款 R P G \mathrm{RPG} RPG 游戏。他的角色一共有 N N N 个可以加攻击力的技能。其中第 i i i 个技能首次升级可以提升 A i A_i Ai 点攻击力,以后每次升级增加的点数都会减少 B i B_i Bi。 ⌈ A i B i ⌉ \lceil\frac{A_i}{B_i}\rceil ⌈BiAi⌉ (上取整) 次之后,再升级该技能将不会改变攻击力。
现在小蓝可以总计升级 M M M 次技能,他可以任意选择升级的技能和次数。请你计算小蓝最多可以提高多少点攻击力?
【输入格式】
输入第一行包含两个整数 N N N 和 M M M。
以下 N N N 行每行包含两个整数 A i A_i Ai 和 B i B_i Bi。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
3 6
10 5
9 2
8 1
【样例输出】
47
【评测用例规模与约定】
对于
40
%
40\%
40% 的评测用例,
1
≤
N
,
M
≤
1000
1 ≤ N, M ≤ 1000
1≤N,M≤1000;
对于
60
%
60\%
60% 的评测用例,
1
≤
N
≤
1
0
4
,
1
≤
M
≤
1
0
7
1 ≤ N ≤ 10^4, 1 ≤ M ≤ 10^7
1≤N≤104,1≤M≤107;
对于所有评测用例,
1
≤
N
≤
1
0
5
,
1
≤
M
≤
2
×
1
0
9
,
1
≤
A
i
,
B
i
≤
1
0
6
1 ≤ N ≤ 10^5,1 ≤ M ≤ 2 × 10^9,1 ≤ A_i, B_i ≤ 10^6
1≤N≤105,1≤M≤2×109,1≤Ai,Bi≤106。
二分
签到 × 7 ×\ 7 × 7。
第 i i i 个技能,在升级第 k k k 次时提升的攻击力为 A i − ( k − 1 ) B i A_i -(k - 1)B_i Ai−(k−1)Bi,呈单调递减,也就是说,将所有可能的提升 A i − k B i A_i -kB_i Ai−kBi 排成一个单调不上升序列,我们只取前 M M M 项是显然合法的。
故我们二分前 M M M 项的最小值,若所有不小于它的增量个数大于 M M M,我们另 r i g h t = m i d right = mid right=mid,否则另 l e f t = m i d + 1 left = mid + 1 left=mid+1,对于计算大于 M M M 的增量个数, A i A_i Ai 到 m i d mid mid 之间可选的升级次数为 ⌈ A i − m i d + 1 B i ⌉ \lceil\frac{A_i-mid+1}{B_i}\rceil ⌈BiAi−mid+1⌉,然后特判一下 ∑ i = 1 n B i \sum_{i=1}^nB_i ∑i=1nBi 不大于 M M M,
签到题,没什么好讲的。
#include <stdio.h>
const int N = 100000;
long long ans, buf, tmp;
int n, m, A[N], B[N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; ++i) {
scanf("%d %d", &A[i], &B[i]);
tmp = (A[i] + B[i] - 1) / B[i];
ans += tmp * A[i] - (tmp - 1) * tmp / 2 * B[i];
buf += tmp;
}
if (buf <= m) { printf("%lld", ans); return 0; }
int left = 1, right = 1000000, mid;
while (left < right) {
mid = left + right + 1 >> 1, buf = 0;
for (int i = 0; i < n; ++i)
if (A[i] >= mid) buf += (A[i] - mid) / B[i] + 1;
if (buf >= m) left = mid; else right = mid - 1;
}
ans = 0, buf = 0;
for (int i = 0; i < n; ++i)
if (A[i] > left) {
tmp = (A[i] - left + B[i] - 1) / B[i];
ans += tmp * A[i] - (tmp - 1) * tmp / 2 * B[i];
buf += tmp;
}
printf("%lld", ans + (m - buf) * left);
}
试题 J: 重复的数
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 25 25 25 分
【问题描述】
给定一个数列 A = ( a 1 , a 2 , ⋯ , a n ) A = (a_1, a_2,\cdots, a_n) A=(a1,a2,⋯,an),给出若干询问,每次询问某个区间 [ l i , r i ] [l_i,r_i] [li,ri] 内恰好出现 k i k_i ki 次的数有多少个。
【输入格式】
输入第一行包含一个整数 n n n 表示数列长度。
第二行包含 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1, a_2,\cdots, a_n a1,a2,⋯,an,表示数列中的数。
第三行包含一个整数 m m m 表示询问次数。
接下来 m m m 行描述询问,其中第 i i i 行包含三个整数 l i , r i , k i l_i, r_i, k_i li,ri,ki 表示询问 [ l i , r i ] [l_i,r_i] [li,ri] 区间内有多少数出现了 k i k_i ki 次。
【输出格式】
输出 m m m 行,分别对应每个询问的答案。
【样例输入】
3
1 2 2
5
1 1 1
1 1 2
1 2 1
1 2 2
1 3 2
【样例输出】
1
0
2
0
1
【评测用例规模与约定】
对于
20
%
20\%
20% 的评测用例,
n
,
m
≤
500
,
1
≤
a
1
,
a
2
,
⋯
,
a
n
≤
1000
n, m ≤ 500, 1 ≤ a_1, a_2,\cdots, a_n ≤ 1000
n,m≤500,1≤a1,a2,⋯,an≤1000;
对于
40
%
40\%
40% 的评测用例,
n
,
m
≤
5000
n, m ≤ 5000
n,m≤5000;
对于所有评测用例,
1
≤
n
,
m
≤
100000
,
1
≤
a
1
,
a
2
,
⋅
⋅
⋅
,
a
n
≤
100000
,
1
≤
l
i
≤
r
i
≤
n
,
1
≤
k
i
≤
n
1 ≤ n, m ≤ 100000, 1 ≤ a_1, a_2, · · · , a_n ≤ 100000, 1 ≤ l_i ≤ r_i ≤ n, 1 ≤ k_i ≤ n
1≤n,m≤100000,1≤a1,a2,⋅⋅⋅,an≤100000,1≤li≤ri≤n,1≤ki≤n。
莫队分块
签到 × 8 ×\ 8 × 8。
模板题。
#include <stdio.h>
#include <algorithm>
#include <math.h>
const int N = 100001;
int n, m, block, A[N], map[N], cnt[N], ans[N];
struct query {
int i, l, r, k;
inline bool operator<(const query &q) const { return l / block == q.l / block ? (l / block & 1 ? q.r < r : r < q.r) : l < q.l; }
} Q[N];
inline void add(int a) { --cnt[map[a]]; ++cnt[++map[a]]; }
inline void del(int a) { --cnt[map[a]]; ++cnt[--map[a]]; }
int main() {
scanf("%d", &n), block = sqrt(n);
for (int i = 1; i <= n; ++i) scanf("%d", &A[i]);
scanf("%d", &m);
for (int i = 0; i < m; ++i)
Q[i].i = i, scanf("%d %d %d", &Q[i].l, &Q[i].r, &Q[i].k);
std::sort(Q, &Q[m]);
int l = 1, r = 0;
for (int i = 0; i < m; ++i) {
while (r < Q[i].r) add(A[++r]);
while (l > Q[i].l) add(A[--l]);
while (r > Q[i].r) del(A[r--]);
while (l < Q[i].l) del(A[l++]);
ans[Q[i].i] = cnt[Q[i].k];
}
for (int i = 0; i < m; ++i) printf("%d\n", ans[i]);
}