第十三届蓝桥杯大赛软件赛省赛(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=a1a2+a1a3++a1an+a2a3++an2an1+an2an+an1an

【输入格式】

  输入的第一行包含一个整数 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 1n10001ai100
  对于所有评测用例, 1 ≤ n ≤ 200000 , 1 ≤ a i ≤ 1000 1 ≤ n ≤ 200000,1 ≤ a_i ≤ 1000 1n2000001ai1000


公式递推


  将 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)+a2a3++an2an1+an2an+an1an

  同样的将余项中包含 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)++an1an=a1i=2nai+a2i=3nai++an1i=nnai

  也就 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=a1i=10ai+a2i=11ai++ani=1n1ai

#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 nm,请问对 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 1mn300
  对于 50 % 50\% 50% 的评测用例, 1 ≤ m ≤ n ≤ 1000 1 ≤ m ≤ n ≤ 1000 1mn1000
  对于所有评测用例, 1 ≤ m ≤ n ≤ 1 0 6 1 ≤ m ≤ n ≤ 10^6 1mn106


  签到 ×   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 1n,m100
  对于 40 % 40\% 40% 的评测用例, 1 ≤ n , m ≤ 1000 1 ≤ n, m ≤ 1000 1n,m1000
  对于所有评测用例, 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} 1n,m1000000x<2201lirin0Ai<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 frkgr 使得 A k ⊕ A g = x A_k \oplus A_g =x AkAg=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 Arx,其中最靠 r r r 的是 f r f_r fr 的一个候选值,同时我们还要考虑 [ f r − 1 , r − 1 ] [f_{r-1},r-1] [fr1,r1] 中是否有更优的方案,最终有状态转移方程: 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{fr1,max{iAi=Arx}}

#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=Si1 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=Si1 S i = S i + 1 S_i = S_{i+1} Si=Si+1,则 S i − 1 S_{i−1} Si1 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 S103 ,其中 ∣ S ∣ |S| S 表示 S S S 的长度;
  对于 50 % 50\% 50% 的评测用例, ∣ S ∣ ≤ 1 0 4 |S| ≤ 10^4 S104
  对于 75 % 75\% 75% 的评测用例, ∣ S ∣ ≤ 1 0 5 |S| ≤ 10^5 S105
  对于所有评测用例, ∣ S ∣ ≤ 1 0 6 |S| ≤ 10^6 S106 S S S 中仅含小写字母。


  签到 ×   5 ×\ 5 × 5

  如果我们将连续相同的字符划分在一块同时操作,

  考虑最差情况,一、所有字符都相同,那么单次操作次数为 1 1 1,操作完 ∣ S ∣ 2 \frac{|S|}2 2S 次后字符串不再变化;二、字符全部被划分长度为 2 2 2 的连续块,单次操作次数为 ∣ S ∣ 2 \frac{|S|}2 2S,操作完 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 LiRi ,相邻两个整数之间用一个空格分隔。

【输出格式】

  输出一行包含一个整数表示答案。

【样例输入】

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,m50
  对于 50 % 50\% 50% 的评测用例, n , m ≤ 500 n, m ≤ 500 n,m500
  对于 70 % 70\% 70% 的评测用例, n , m ≤ 5000 n, m ≤ 5000 n,m5000
  对于所有评测用例, 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 1n,m1051Ai1061LiRi106


差分


  签到 ×   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 1N,M1000
  对于 60 % 60\% 60% 的评测用例, 1 ≤ N ≤ 1 0 4 , 1 ≤ M ≤ 1 0 7 1 ≤ N ≤ 10^4, 1 ≤ M ≤ 10^7 1N104,1M107
  对于所有评测用例, 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 1N1051M2×1091Ai,Bi106


二分


  签到 ×   7 ×\ 7 × 7

  第 i i i 个技能,在升级第 k k k 次时提升的攻击力为 A i − ( k − 1 ) B i A_i -(k - 1)B_i Ai(k1)Bi,呈单调递减,也就是说,将所有可能的提升 A i − k B i A_i -kB_i AikBi 排成一个单调不上升序列,我们只取前 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 BiAimid+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,m500,1a1,a2,,an1000
  对于 40 % 40\% 40% 的评测用例, n , m ≤ 5000 n, m ≤ 5000 n,m5000
  对于所有评测用例, 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 1n,m100000,1a1,a2,,an100000,1lirin,1kin


莫队分块


  签到 ×   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]);
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值