CSP-J2019普及组初赛难点整理

  1. 8 8 8 个同样的球放在 5 5 5 个同样的袋子里,允许有的袋子空着不放,问共有多少种不同的分法?( 18)
    提示:如果 8 8 8 个球都放在一个袋子里,无论是哪个袋子,都只算同一种分法。

【解析】 n n 个相同的球放入 m m 个相同的盒子( n ≥ m n≥m nm),可以有空盒时的放法种数等于将 n n 分解为 m m 个、 ( m - 1 ) (m-1) (1)个、 ( m - 2 ) (m-2) (2)个、…、 2 2 2个、 1 1 1个数的和的所有种数之和。

8=8 8=1+7,8=2+6,8=3+5,8=4+4
8=1+1+6,8=1+2+5,8=1+3+4,8=2+2+4,8=2+3+3
8=1+1+1+5,8=1+1+2+4,8=1+1+3+3,8=1+2+2+3,8=2+2+2+2
8=1+1+1+1+4,8=1+1+1+2+3,8=1+1+2+2+2

  1. 新学期开学了,小胖想减肥,健身教练给小胖制定了两个训练方案。方案一:每次连续跑 3 3 3 公里可以消耗 300 300 300 千卡(耗时半小时);方案二:每次连续跑 5 5 5 公里可以消耗 600 600 600 千卡(耗时 1 1 1 小时)。小胖每周周一到周四能抽出半小时跑步,周五到周日能抽出一小时跑步。另外,教练建议小胖每周最多跑 21 21 21 公里,否则会损伤膝盖,每周最多通过跑步消耗多少千卡( )。

【解析】每周最多跑 21 21 21公里,所以优先安消耗卡路里最高的运动方案,即周五、六、日跑步一小时,一共可以跑 15 15 15公里,消耗1800千卡;剩余6公里安排在周一到周四跑两天即可,消耗600千卡,一共消耗2400千卡。

  1. 一副纸牌除掉大小王有 52 52 52 张牌,四种花色,每种花色 13 13 13 张。假设从这 52 52 52张牌中随机抽取 13 13 13 张纸牌,则至少( )张牌的花色一致。

【解析】为了保证每种花色的牌最少,那么将 13 13 13张扑克分成 4 4 4份,每种花色各 3 3 3张,这样还剩 1 1 1张,根据鸽巢原理,必然有 1 1 1张扑克和其它 3 3 3张同花色,则至少( 4)张牌的花色一致。

  1. 一些数字可以颠倒过来看,例如 0 0 0 1 1 1 8 8 8 颠倒过来还是本身, 6 6 6 颠倒过来是 9 9 9 9 9 9 颠倒过来看还是 6 6 6,其他数字颠倒过来都不构成数字。类似的,一些多位数也可以颠倒过来看,比如 106 106 106 颠倒过来是 901 901 901。假设某个城市的车牌只由 5 5 5 位数字组成,每一位都可以取 0 0 0 9 9 9。请问这个城市最多有多少个车牌倒过来恰好还是原来的车牌?( )

【解析】如果5位数的车牌倒过来恰好还是原来的数字,需要满足两个条件:

  • 中间位的数字只能是 0 0 0 1 1 1 8 8 8
  • 左边两位数字可以是 0 0 0 1 1 1 8 8 8 6 6 6 9 9 9任意一个,确定了左边的数字,右边的数字选择旋转后对应的数字即可。

答案: C 3 1 × C 5 1 × C 5 1 = 75 C_3^1\times C_5^1 \times C_5^1=75 C31×C51×C51=75

  1. 假设一棵二叉树的后序遍历序列为 DGJHEBIFCA,中序遍历序列为 DBGEHJACIF,则其前序遍历序列为( )。

【解析】后序序列和中序序列可以惟一确定一棵二叉树。

在这里插入图片描述

该二叉树的前序序列为:ABDEGHJCFI

阅读程序

#include <cstdio>
#include <cstring>
using namespace std;
char st[100];
int main() {
    
    
    scanf("%s", st);
    int n = strlen(st);
    for (int i = 1; i <= n; ++i) {
    
    
        if (n % i == 0) {
    
    
            char c = st[i - 1];
            if (c >= 'a')
                st[i - 1] = c - 'a' + 'A';
        }
    }
    printf("%s", st);
    return 0;
}
  • 若输入的字符串长度为 18,那么输入的字符串跟输出的字符串相比,至多有( )个字符不同。

【解析】即求 1 1 1~ 18 18 1818的约数个数。
任何一个大于 1 1 1的自然数 N N N,如果 N N N不为质数,那么 N N N可以唯一分解成有限个质数的乘积: N = P 1 a 1 × P 2 a 2 × P 3 a 3 × . . . × P n a n N = P_{1}^{a_1} × P_{2}^{a_2} × P_{3}^{a_3} × ... × P_{n}^{a_n} N=P1a1×P2a2×P3a3×...×Pnan,这里 P 1 < P 2 < P 3 < . . . < P n P_1 < P_2 < P_3 < ... < P_n P1<P2<P3<...<Pn均为质数,其中指数 a i a_i ai是正整数。这样的分解称为 N N N 的标准分解式。 那么 N N N 的约数个数 = ( a 1 + 1 ) × ( a 2 + 1 ) × ( a 3 + 1 ) × . . . × ( a n + 1 ) = (a_1 + 1) × (a_2 + 1) × (a_3 +1) × ... × (a_n + 1) =(a1+1)×(a2+1)×(a3+1)×...×(an+1)
18标准分解式: 18 = 2 1 × 3 2 18=2^1\times3^2 18=21×32
18的约数个数 = ( 1 + 1 ) × ( 2 + 1 ) = 6 =(1+1)\times(2+1)=6 =(1+1)×(2+1)=6

  • 若输入的字符串长度为( ),那么输入的字符串跟输出的字符串相比,至多有 36 36 36 个字符不同。

【解析】 100000 100000 100000的标准分解式 = 2 5 × 5 5 =2^5\times5^5 =25×55100000的约数个数 = ( 5 + 1 ) × ( 5 + 1 ) = 36 =(5+1)\times(5+1)=36 =(5+1)×(5+1)=36

#include<cstdio>
using namespace std;
int n, m;
int a[100], b[100];

int main() {
    
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
        a[i] = b[i] = 0;
    for (int i = 1; i <= m; ++i) {
    
    
        int x, y;
        scanf("%d%d", &x, &y);
        if (a[x] < y && b[y] < x) {
    
     //第13行
            if (a[x] > 0)
                b[a[x]] = 0; //第15行
            if (b[y] > 0)
                a[b[y]] = 0;
            a[x] = y;
            b[y] = x;
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
    
    
        if (a[i] == 0)
            ++ans;
        if (b[i] == 0)
            ++ans; //第27行
    }
    printf("%d", ans);
    return 0;
}
  • 假设输入的 n n n m m m 都是正整数, x x x y y y 都是在 [ 1 , n ] [1, n] [1,n] 的范围内的整数。
    执行完第 27 27 27 行的 ++ansans 一定是偶数。

【解析】由于 x x x y y y都是在 [ 1 , n ] [1, n] [1,n] 范围内的整数,所以无论如何取值,ans最终都是偶数。但是题目中问的是执行完第 27 27 27 行的 ++ans的情况,27行在循环内部,当m= 1 , x= 1, y = 2时,for循环第一次执行到27行时,ans = 1,为奇数。

  • 假设输入的 n n n m m m 都是正整数, x x x y y y 都是在 [ 1 , n ] [1, n] [1,n]的范围内的整数。a[i]b[i] 不可能同时大于 0

【解析】当m= 1 , x= 1, y = 1时,a[1]b[1] 都为1,同时大于 0

  • 假设输入的 n n n m m m 都是正整数, x x x y y y 都是在 [ 1 , n ] [1, n] [1,n]的范围内的整数。若程序执行到第 13 13 13行时, x x x 总是小于 y y y,那么第 15 15 15 行不会被执行。

【解析】当m= 2,第一次循环x= 1, y = 2,此时a[1] = 2, b[2] = 1;第二次循环 x = 1, y = 3时, a[1] < 3 && b[3] < 1a[1] > 0,第15行被执行。

  • m m m x x x 两两不同,且 m m m y y y 两两不同,则输出的值为( )。

【解析】若 m m m x x x 两两不同,且 m m m y y y,其值可以是x = 1, y = 1x = 2, y = 2,…,x = m, y = m,输入结束后a[1] = 1, b[1] = 1, a[2] = 2, b[2] = 2, ... , a[m] = m, b[m] = m,数组a[]b[]中各有m个非零的数,此时ans = 2n - 2m

  • m m m x x x 两两不同,且 m m m y y y 都相等,则输出的值为( )。

【解析】若 m m m x x x 两两不同,且 m m m y y y 都相等,其值可以是x = 1, y = 1x = 2, y = 1,…,x = m, y = 1,输入结束后a[1] = 0, b[1] = m, a[2] = 0, b[2] = 0, ... , a[m] = 1, b[m] = 0,除了a[m] = 1b[1] = m,数组其它值都为0,一共有2个非零的数,此时ans = 2n - 2

#include <iostream>
using namespace std;
const int maxn = 10000;
int n;
int a[maxn];
int b[maxn];
int f(int l, int r, int depth) {
    
    
    if (l > r)
        return 0;
    int min = maxn, mink;
    for (int i = l; i <= r; ++i) {
    
    
        if (min > a[i]) {
    
    
            min = a[i];
            mink = i;
        }
    }
    int lres = f(l, mink - 1, depth + 1);
    int rres = f(mink + 1, r, depth + 1);
    return lres + rres + depth * b[mink];
}
int main() {
    
    
    cin >> n;
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    for (int i = 0; i < n; ++i)
        cin >> b[i];
    cout << f(0, n - 1, 1) << endl;
    return 0;
}
  • 如果 b b b 数组全为 0 0 0 则输出为 0 0 0

【解析】f()中返回lres + rres + depth * b[mink],如果b[]全为0,结果一定为0。

  • n = 100 n=100 n=100 时,最坏情况下,与第 12 12 12 行的比较运算执行的次数最接近的是:( )

【解析】最坏情况下,即递归树深度最大。例如数组a[]中元素是有序的,递归到下一层只减少一个元素,此时比较的次数为 100 + 99 + . . . + 1 = 5050 100 + 99 + ... + 1 = 5050 100+99+...+1=5050 。所以最坏情况下,比较运算执行的次数最接近的是6000

  • n = 100 n=100 n=100 时,最好情况下,与第 12 12 12 行的比较运算执行的次数最接近的是:( )

【解析】最好情况下,递归调用的深度应尽可能的小,每次二分时左右区间的长度相同。此时,递归树应是一棵完全二叉树,且高度为 ⌊ l o g 2 100 ⌋ + 1 = 7 \lfloor log_2^{100} \rfloor + 1 = 7 log2100+1=7
比较次数大约为 100 + 50 × 2 + 25 × 4 + 12 × 8 + 6 × 16 + 3 × 32 + 1 × 37 ≈ 100 + 50\times2 + 25\times4 + 12\times8+6\times16+3\times32+1\times37≈ 100+50×2+25×4+12×8+6×16+3×32+1×37 600

  • n = 10 n=10 n=10 时,若 b b b 数组满足,对于任意 0 ≤ i < n 0 \le i < n 0i<n,都有 b[i] = i + 1,那么输出最大为( )。

【解析】已知b[0] = 1, b[1] = 2, ..., b[9] = 10,要输出最大值,即depth * b[mink]的和最大,那么depth的值应该尽可能的大。与上道题类似,数组a[]中元素是有序的,才能保证归深度最大,最大值 = 1 ×   1 + 2 × 2 + 3 × 3 + . . . + 10 × 10 = 385 =1 \times\ 1+ 2 \times 2 + 3 \times 3 + ... + 10 \times 10=385 =1× 1+2×2+3×3+...+10×10=385

  • n = 100 n=100 n=100 时,若 b b b 数组满足,对于任意 0 ≤ i < n 0 \le i < n 0i<n,都有 b[i] = 1,那么输出最小为( )。

【解析】已知b[i] = 1,要输出最小值,即depth * b[mink]的和最小,那么depth的值应该尽可能的小,此时递归树应是一棵完全二叉树,且高度为 ⌊ l o g 2 100 ⌋ + 1 = 7 \lfloor log_2^{100} \rfloor + 1 = 7 log2100+1=7
如果是 7 7 7层的满二叉树总结点数应该是 2 7 − 1 = 127 2^7 - 1 =127 271=127,现在少了 27 27 27个,那么第 7 7 7层一共有 2 7 − 1 − 27 = 37 2^{7-1}-27=37 27127=37个结点。
最终结果 = 37 × 7 + 32 × 6 + 16 × 5 + 8 × 4 + 4 × 3 + 2 × 2 + 1 × 1 = =37\times7+32\times6+16\times5+8\times4+4\times3+2\times2+1\times1= =37×7+32×6+16×5+8×4+4×3+2×2+1×1=580

完善程序

  1. (矩阵变换)有一个奇幻的矩阵,在不停的变幻,其变幻方式为:数字 0 0 0 变成矩阵 [ 0 0 0 1 ] \begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix} [0001] ,数字 1 1 1 变成矩阵 [ 1 1 1 0 ] \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} [1110] 。最初该矩阵只有一个元素 0 0 0,变幻 n n n 次后,矩阵会变成什么样?
    例如,矩阵最初为: [ 0 ] [ 0 ] [0],矩阵变幻一次后: [ 0 0 0 1 ] \begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix} [0001] ;矩阵变幻 2 2 2 次后: [ 0 0 0 0 0 1 0 1 0 0 1 1 0 1 1 0 ] \begin{bmatrix} 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 1 \\0 & 0 & 1 & 1 \\ 0 & 1 & 1 & 0\end {bmatrix} 0000010100110110
    输入一行一个不超过 10 10 10 的正整数 nn。输出变幻 n n n次后的矩阵。试补全程序。
    提示:<< 表示二进制左移运算符,例如 ( 11 ) 2 < < 2 = ( 1100 ) 2 (11)_{2}<<2=(1100)_2 (11)2<<2=(1100)2
    ^ 表示二进制异或运算符,它将两个运算的数中的每个对应的二进制位一一进行比较,若两个二进制位相同,则运算结果的对应二进制位为 0,反之为 1
#include <cstdio>
using namespace std;
int n;
const int max_size = 1 << 10;

int res[max_size][max_size];

void recursive(int x, int y, int n, int t) {
    
    
    if (n == 0) {
    
    
        res[x][y] =;
        return;
    }
    int step = 1 << (n - 1);
    recursive(, n - 1, t);
    recursive(x, y + step, n - 1, t);
    recursive(x + step, y, n - 1, t);
    recursive(, n - 1, !t);
}

int main() {
    
    
    scanf("%d", &n);
    recursive(0, 0,);
    int size =;
    for (int i = 0; i < size; i++) {
    
    
        for (int j = 0; j < size; j++)
            printf("%d", res[i][j]);
        puts("");
    }
    return 0;
}

【解析】分治思想,将以 ( x , y ) (x,y) (x,y)开头的矩阵变成4部分,分别进行递归处理。函数recursive(int x, int y, int n, int t)x、y表示矩阵的起点,n表示要变换的次数,t表示要填入的0、1值。step表示变换后矩阵对应元素距离。变换1次后,矩阵变为 2 × 2 2\times2 2×2,对应元素的的距离为1;变化2次后,矩阵变为 4 × 4 4\times4 4×4,对应元素的距离为2…。

空①,递归结束条件,当n == 0时,此时:res[x][y] = t
空②,递归处理左上角的矩阵,其左上角的坐标就是:x,y
空③,递归处理右下角的矩阵,其左上角的坐标为:x+step,y+step
空④,初始情况:n, 0
空⑤:经过n次变换后,矩阵的大小为 2 n × 2 n 2^n\times2^n 2n×2nsize为矩阵的行列个数,所以次空应为1<<n

  1. (计数排序)计数排序是一个广泛使用的排序方法。下面的程序使用双关键字计数排序,对 n n n 10000 10000 10000 以内的整数,从小到大排序。
    例如有三对整数(3,4)、(2,4)、(3,3),那么排序之后应该是(2,4)、(3,3)、(3,4)。
    输入第一行为 n n n,接下来 n n n 行,第 i i i 行有两个数 a [ i ] a[i] a[i] b [ i ] b[i] b[i],分别表示第 i i i 对整数的第一关键字和第二关键字。
    数据范围 1 ≤ n ≤ 1 0 7 , 1 ≤ a [ i ] , b [ i ] ≤ 1 0 4 1 \leq n \leq 10^7,1 \leq a[i], b[i] \leq 10^4 1n107,1a[i],b[i]104
    提示:应先对第二关键字排序,再对第一关键字排序。数组 ord[] 存储第二关键字排序的结果,数组 res[] 存储双关键字排序的结果。试补全程序。
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 10000000;
const int maxs = 10000;

int n;
unsigned a[maxn], b[maxn],res[maxn], ord[maxn];
unsigned cnt[maxs + 1];
int main() {
    
    
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
        scanf("%d%d", &a[i], &b[i]);
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; ++i); // 利用 cnt 数组统计数量
    for (int i = 0; i < maxs; ++i)
        cnt[i + 1] += cnt[i];
    for (int i = 0; i < n; ++i); // 记录初步排序结果
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; ++i); // 利用 cnt 数组统计数量
    for (int i = 0; i < maxs; ++i)
        cnt[i + 1] += cnt[i];
    for (int i = n - 1; i >= 0; --i)// 记录最终排序结果
    for (int i = 0; i < n; i++)
        printf("%d %d",);

    return 0;
}

【解析】计数排序,通过统计每个待排序元素的出现次数,实现排序。本题中,cnt[]数组分别统计了两个关键字数组中每个元素的出现次数,同时又进一步处理了在区间 [ 0 , 10000 ) [0,10000) [0,10000)中,前i个数中小于等于i的关键字个数,可以计算每个关键字的排名。
数组 ord[] 和数组 res[] 都是以排名为下标,记录关键字在数组中的位置。数组 ord[] 存储第二关键字排序的结果,ord[i] = j表示排名为i的第二个关键字在数组b[j]中。数组 res[] 存储双关键字排序的结果,res[i] = j表示最终排名为i的关键字在数组a[j]、b[j]中。

空①,题目中提示先对第二个关键字排序,所以次空应填:cnt[b[i]]++,记录所有第二个关键字的出现次数。
空②,记录初步排序结果。通过上面代码cnt[i + 1] += cnt[i];cnt[i]存储了小于等于i的第二关键字的个数,那么cnt[b[i]]即表示小于等于关键字b[i]的关键字个数,即b[i]的排名。题目中提示ord[]存储第二关键字排序的结果,此处应将处理结果保存到数组ord[]中,即ord[--cnt[b[i]]] = i--cnt[b[i]保证了排名从0~n-1,同时,b[i]的出现次数减少一次。
空③,利用 cnt 数组统计第一个关键字的数量,所以次空应填:cnt[a[i]]++
空④,通过上面代码cnt[i + 1] += cnt[i];cnt[i]存储了小于等于i的第一关键字的个数,cnt[a[i]]即表示小于等于关键字a[i]的关键字个数,即a[i]的排名。cnt[a[ord[i]]]表示排名为i的第二关键字在数组位置ord[i],其对应的双关键字的排名为cnt[a[ord[i]]],它所在数组的位置为ord[i]。所以此空应填:res[--cnt[a[ord[i]]]] = ord[i]
空⑤,数组 res[] 存储双关键字排序的结果,所以次空应填:a[res[i]], b[res[i]]

猜你喜欢

转载自blog.csdn.net/qiaoxinwei/article/details/107957077