NOIP2018提高组初赛难点整理

选择题

  1. 在一条长度为 1 1 1 的线段上随机取两个点,则以这两个点为端点的线段的期望长度是 ( 1 3 \frac{1}{3} 31 ) 。

【解析】
数学期望:若离散型随机变量 X X X的概率分布为

X X X x 1 x_1 x1 x 2 x_2 x2 . . . ... ... x i x_i xi . . . ... ... x n x_n xn
P P P p 1 p_1 p1 p 2 p_2 p2 . . . ... ... p i p_i pi . . . ... ... p n p_n pn

那么 E ( x ) = x 1 × p 1 + x 2 × p 2 + . . . + x n × p n E(x) = x_1 × p_1 + x_2 × p_2 + ... + x_n × p_n E(x)=x1×p1+x2×p2+...+xn×pn为随机变量 X X X的均值或称数学期望。

本题可以使用排除法,如果左端点固定在了最左边那么答案为 1 2 \frac{1}{2} 21,既然左端点更大,那么肯定答案会小于 1 2 \frac{1}{2} 21,因此只能是 1 3 \frac{1}{3} 31

  1. 假设一台抽奖机中有红、蓝两色的球,任意时刻按下抽奖按钮,都会等概率获得红球或蓝球之一。有足够多的人每人都用这台抽奖机抽奖,假如他们的策略均为:抽中蓝球则继续抽球,抽中红球则停止。最后每个人都把自己获得的所有球放到一个大箱子里,最终大箱子里的红球与蓝球的比例接近于1:1

【解析】数学期望解题技巧:

  1. 确定取值:写出 X X X可能取的全部值
  2. 求概率: X X X每个值的概率
  3. 写出 X X X的分布列
  4. 由均值的定义求出 E ( X ) E(X) E(X)

获得红球的数量概率分布为:

X X X 第1次抽中红球 第2次抽中红球 . . . ... ... i i i次抽中红球
P P P 1 2 \frac{1}{2} 21 ( 1 2 ) 2 (\frac{1}{2})^2 (21)2 . . . ... ... ( 1 2 ) i (\frac{1}{2})^i (21)i

获得红球数量的数学期望 E ( r e d ) = ∑ i = 1 ∞ ( 1 2 ) i E(red)=\sum\limits_{i=1}^\infty(\frac{1}{2})^i E(red)=i=1(21)i。等比数列求和,当 n → ∞ n\rightarrow \infty n时, E ( r e d ) = 1 E(red)=1 E(red)=1。 同理,获得蓝球数量的数学期望 E ( b l u e ) = 1 E(blue) = 1 E(blue)=1

最终大箱子里的红球与蓝球的比例接近1:1

  1. 为了统计一个非负整数的二进制形式中 1 1 1 的个数,代码如下:
int CountBit(int x)
{
    
    
     int ret = 0;
     while (x)
     {
    
    
       ret++;
       ________;
     }
     return ret;
}

【解析】x & = x - 1可以将一个非负整数x最低位的1变为0。除此之外,x -= lowbit(x)也可以实现同样的效果,lowbit(x) = x & -x

  1. 2−3 树是一种特殊的树,它满足两个条件:
    1)每个内部结点有两个或三个子结点;
    2)所有的叶结点到根的路径长度相同。
    如果一棵 2-3 树有 10 个叶结点,那么它可能有 ( ) 个非叶结点。
    【解析】符合上述条件的2−3 树,非叶结点的个数有87两类,例如:
    在这里插入图片描述
    在这里插入图片描述

问题求解

方程 a ∗ b = ( a   o r   b ) ∗ ( a   a n d   b ) a*b =(a\ or\ b) * (a\ and\ b) ab=(a or b)(a and b),在 a , b a,b a,b 都取 [ 0 , 31 ] [0, 31] [0,31] 中的整数时,共有(454)组解。(∗ 表示乘法;or 表示按位或运算;and 表示按位与运算)。

【解析】满足等式 a ∗ b = ( a   o r   b ) ∗ ( a   a n d   b ) a*b =(a\ or\ b) * (a\ and\ b) ab=(a or b)(a and b) a 、 b a、b ab有如下关系:一个数的二进制中为1位的在另一个数相应的二进制为上也是1,例如:3和7, ( 011 ) 2 (011)_2 (011)2 ( 111 ) 2 (111)_2 (111)2 3 × 7 = ( 3 & 7 ) × ( 3 ∣ 7 ) = 7 × 3 = 21 3 \times 7=(3 \& 7) \times (3|7)=7\times3=21 3×7=(3&7)×(37)=7×3=21
根据 a a a1的情况进行分类:

  • 有0个1, b b b 2 5 2^5 25种方案, a 和 b a和b ab方案有 1 × 32 = 32 1\times32=32 1×32=32

  • 有1个1, a a a C 5 1 C_5^1 C51种方案, a 和 b a和b ab方案有 C 5 1 × ( 16 + 1 ) = 85 C_5^1\times(16+1)=85 C51×(16+1)=85种,因为b可以有下面情况:

    • b b b的对应位为1,有 2 4 2^4 24种方案,
    • b b b为0,有1种方案
  • 有2个1, a 和 b a和b ab C 5 2 × ( 8 + 2 + 1 ) = 110 C_5^2\times(8+2+1)=110 C52×(8+2+1)=110种,因为b可以有下面情况:

    • b b b的对应位有2个1,有 2 3 2^3 23种方案
    • b b b的对应位有1个1,有 C 2 1 C_2^1 C21种方案
    • b b b的对应位有0个1,有1种方案
  • 有3个1, a 和 b a和b ab C 5 3 × ( 4 + 3 + 3 + 1 ) = 110 C_5^3\times(4+3+3+1)=110 C53×(4+3+3+1)=110种,因为b可以有下面情况:

    • b b b的对应位有3个1, b b b 2 2 2^2 22种方案
    • b b b的对应位有2个1, b b b C 3 2 C_3^2 C32种方案
    • b b b的对应位有1个1, b b b C 3 1 C_3^1 C31种方案
    • b b b的对应位有0个1, b b b有1种方案
  • 有4个1, a 和 b a和b ab方案有 C 5 4 × ( 2 + 4 + 6 + 4 + 1 ) = 85 种 C_5^4\times(2+4+6+4+1)=85种 C54×(2+4+6+4+1)=85,因为b可以有下面情况:

    • b b b的对应位有4个1, b b b 2 1 2^1 21种方案
    • b b b的对应位有3个1, b b b C 4 3 C_4^3 C43种方案
    • b b b的对应位有2个1, b b b C 4 2 C_4^2 C42种方案
    • b b b的对应位有1个1, b b b C 4 1 C_4^1 C41种方案
    • b b b的对应位有0个1, b b b 1 1 1种方案
  • 有5个1, b b b 32 32 32种方案, a 和 b a和b ab方案有 1 × 32 = 1 1\times32=1 1×32=1

答案: ( 32 + 85 + 110 ) × 2 = 454 (32+85+110)\times2=454 (32+85+110)×2=454

阅读程序写结果

#include <cstdio>
int n, d[100];
bool v[100];
int main(){
    
    
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
    
    
        scanf("%d", d + i);
        v[i] = false;
    }
    int cnt = 0;
    for (int i = 0; i < n; ++i){
    
    
        if (!v[i]){
    
    
            for (int j = i; !v[j]; j = d[j]){
    
    
                v[j] = true;
            }
            ++cnt;
        }
    }
    printf("%d", cnt);
    return 0;
}

输入:10 7 1 4 3 2 5 9 8 0 6

【解析】手动模拟

i 0 1 2 3 4 5 6 7 8 9
d[i] 7 1 4 3 2 5 9 8 0 6
  • i = 0v[0] = true, v[7] = true, v[8] = truecnt = 1
  • i = 1v[1] = truecnt = 2
  • i = 2v[2] = true, v[4] = truecnt = 3
  • i = 3v[3] = truecnt = 4
  • i = 4
  • i = 5v[5] = truecnt = 5
  • i = 6v[6] = true, v[9] = truecnt = 6
  • i = 7
  • i = 8
  • i = 9

答案:6

#include <iostream>
using namespace std;
string s;
long long magic(int l, int r){
    
    
    long long ans = 0;
    for (int i = l; i <= r; ++i){
    
    
        ans = ans * 4 + s[i] - 'a' + 1;
    }
    return ans;
}
int main(){
    
    
    cin >> s;
    int len = s.length();
    int ans = 0;
    for (int l1 = 0; l1 < len; ++l1){
    
    
        for (int r1 = l1; r1 < len; ++r1){
    
    
            bool bo = true;
            for (int l2 = 0; l2 < len; ++l2){
    
    
                for (int r2 = l2; r2 < len; ++r2){
    
    
                    if (magic(l1, r1) == magic(l2, r2) && (l1 != l2 || r1 != r2)){
    
    
                        bo = false;
                    }
                }
            }
            if (bo){
    
    
                ans += 1;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

输入:abacaba

【解析】字符串哈希,将输入字符串[l,r]位置的子串转换为4进制数,查找是否存与前缀字符串哈希值不同的子串。
从0开始的前缀字符串,且不存在哈希值相同的子串有4个:

  • abac
  • abaca
  • abacab
  • abacaba
    从1开始的前缀字符串,且不存在哈希值相同的子串有4个:
  • bac
  • baca
  • bacab
  • bacaba
    从2开始的前缀字符串,且不存在哈希值相同的子串有4个:
  • ac
  • aca
  • acab
  • acaba
    从3开始的前缀字符串,且不存在哈希值相同的子串有4个:
  • c
  • ca
  • cab
  • caba

答案:16

#include <cstdio>
using namespace std;
const int N = 110;
bool isUse[N];
int n, t;
int a[N], b[N];
bool isSmall(){
    
    
    for (int i = 1; i <= n; ++i)
        if (a[i] != b[i]) return a[i] < b[i];
    return false;
}
bool getPermutation(int pos){
    
    
    if (pos > n){
    
    
        return isSmall();
    }
    for (int i = 1; i <= n; ++i){
    
    
        if (!isUse[i]){
    
    
            b[pos] = i; isUse[i] = true;
            if (getPermutation(pos + 1)){
    
    
                return true;
            }
            isUse[i] = false;
        }
    }
    return false;
}
void getNext(){
    
    
    for (int i = 1; i <= n; ++i){
    
    
        isUse[i] = false;
    }
    getPermutation(1);
    for (int i = 1; i <= n; ++i){
    
    
        a[i] = b[i];
    }
}
int main(){
    
    
    scanf("%d%d", &n, &t);
    for (int i = 1; i <= n; ++i){
    
    
        scanf("%d", &a[i]);
    }
    for (int i = 1; i <= t; ++i){
    
    
        getNext();
    }
    for (int i = 1; i <= n; ++i){
    
    
        printf("%d", a[i]);
        if (i == n) putchar('\n'); 
        else putchar(' ');
    }
    return 0;
}

输入1:6 10 1 6 4 5 3 2
输入2:6 200 1 5 3 4 2 6

【解析】算法通过DFS按字典序生成n个数的全排列,当生成的排列组成的序列>输入序列时停止。t表示在输入序列之后,再继续按字典序生成的排列个数。
输入1,当t=10时,求1 6 4 5 3 2后第11个排列,可以一个个向后枚举:

1 2 3 4 5 6 7 8 9 10
1 6 5 2 3 4 1 6 5 2 4 3 1 6 5 3 2 4 1 6 5 3 4 2 1 6 5 4 2 3 1 6 5 4 3 2 2 1 3 4 5 6 2 1 3 4 6 5 2 1 3 5 4 6 2 1 3 5 6 4

答案为:>2 1 3 5 6 4
输入2,当t=200时,求1 5 3 4 2 6后第200个排列,找规律:

  • 15开头的序列之前有72个
  • 153开头的序列之前有6个
  • 1 5 3 2 4 61 5 3 2 6 41 5 3 4 2 6
    那么 153426是字典序中第81个序列,即求字典序中第281个序列:
  • 3开头的序列之前有240个
  • 31开头的序列有24个
  • 321开头的序列有6个
  • 324开头的序列有6个
    那么就要找325开头的第5个序列。
    答案为:3 2 5 6 1 4
1 2 3 4 5
3 2 5 1 4 6 3 2 5 1 6 4 3 2 5 4 1 6 3 2 5 4 6 1 3 2 5 6 1 4

完善程序

  1. 对于一个 1 到 n 的排列 P(即 1 到 n 中每一个数在 P 中出现了恰好一次),令 q i q_i qi为第 i i i 个位置之后第一个比 P i P_i Pi 值更大的位置,如果不存在这样的位置,则 q i = n + 1 q_i = n + 1 qi=n+1。举例来说,如果 n = 5且 P P P 1   5   4   2   3 1\ 5\ 4\ 2\ 3 1 5 4 2 3,则 q 为 2   6   6   5   6 2\ 6\ 6\ 5\ 6 2 6 6 5 6 。下列程序读入了排列 P P P,使用双向链表求解了答案。试补全程序。
    数据范围 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int L[N], R[N], a[N];
int main(){
    
    
    cin >> n;
    for (int i = 1; i <= n; ++i){
    
    
        int x;
        cin >> x;
        ____(1)____;
    }
    for (int i = 1; i <= n; ++i){
    
    
        R[i] = ____(2)____;
        L[i] = i - 1;
    }
    for (int i = 1; i <= n; ++i){
    
    
        L[____(3)____] = L[a[i]];
        R[L[a[i]]] = R[____(4)____];
    }
    for (int i = 1; i <= n; ++i){
    
    
        cout << ____(5)____ << " ";
    }
    cout << endl;
    return 0;
}

【解析】双链表右侧第一个更大值
空①,保存x在链表中的位置,a[x]=i
空②,初始化链表,将R[i]指向它的右侧位置i+1
空③,从小到大将当前最小的数,从双链表中删除,L[R[a[i]]] = L[a[i]]
空④,R[L[a[i]]] => R[a[i]];
空⑤,输出每个位置之后第一个比当前位置值更大的位置,R[i]

  1. 一只小猪要买 N 件物品 (N 不超过 1000)。
    它要买的所有物品在两家商店里都有卖。第 i i i 件物品在第一家商店的价格是 a[i],在第二家商店的价格是 b[i],两个价格都不小于 0 且不超过 10000。如果在第一家商店买的物品的总额不少于 50000,那么在第一家店买的物品都可以打 95 折(价格变为原来的 0.95 倍)。
    求小猪买齐所有物品所需最少的总额。
    输入:第一行一个数 N。接下来 N 行,每行两个数。第 i 行的两个数分别代表 a[i], b[i]
    输出:输出一行一个数,表示最少需要的总额,保留两位小数。
    试补全程序。
#include <cstdio>
#include <algorithm>
using namespace std;

const int Inf = 1000000000;
const int threshold = 50000;
const int maxn = 1000;

int n, a[maxn], b[maxn];
bool put_a[maxn];
int total_a, total_b;
double ans;
int f[threshold];

int main() {
    
    
    scanf("%d", &n);
    total_a = total_b = 0;
    for (int i = 0; i < n; ++i) {
    
    
        scanf("%d%d", a + i, b + i);
        if (a[i] <= b[i]) total_a += a[i];
        else total_b += b[i];
    }
    ans = total_a + total_b;
    total_a = total_b = 0;
    for (int i = 0; i < n; ++i) {
    
    
        if (____(1)____) {
    
    
            put_a[i] = true;
            total_a += a[i];
        }
        else{
    
    
            put_a[i] = false;
            total_b += b[i];
        }
    }
    if (____(2)____) {
    
    
        printf("%.2f", total_a * 0.95 + total_b);
        return 0;
    }
    f[0] = 0;
    for (int i = 1; i < threshold; ++i)
        f[i] = Inf;
    int total_b_prefix = 0;
    for (int i = 0; i < n; ++i) {
    
    
        if (!put_a[i]) {
    
    
            total_b_prefix += b[i];
            for (int j = threshold - 1; j >= 0; --j) {
    
    
                if (____(3)____ >= threshold && f[j] != Inf)
                    ans = min(ans, (total_a + j + a[i]) * 0.95 + ____(4)____);
                f[j] = min(f[j] + b[i], j >= a[i] ? ____(5)____ : Inf);
            }
        }
    }
    printf("%.2f", ans);
    return 0;
}

【解析】本题求买 N 件物品最少需要的总金额。如果在每件物品在a商店打完折后比b商店便宜,那么一定选择在a中买,此时即为最优策略。该最优策略需要满足两个条件:

  • 物品在a商店打完折后比b商店便宜
  • a商店购买的物品的总额不少于 50000
    依据此最优策略可以设计贪心算法求解。

空①,如果在a商店打完折比b商店便宜,a[i] * 0.95 <= b[i]
空②,满足打折条件,即在a商店购买的物品的总额不少于 50000,total_a >= 50000
如果不满足上述最优策略,使用的是动态规划求解,策略把原先在b商店买的东西换成在a商店买,以获得折扣,求最优解。f[i][j]表示对于前i个物品,额外a店花了j元的情况下,购买b店物品花费的最小值。题中的使用了01背包的空间优化方法,去掉了数组的第一维。
空③,如果第i件物品没有在a店买,并且a店之前花的总金额+在不同金额j的情况下+第i件物品在a店的价格,不低于50000,那么可以打折,即尝试将在a店购买第i件物品是否可以更新ans。所以空③填total_a + j + a[i]
空④,可以在a店买第i件物品时,尝试用新的总金额(在a店花的钱+在b店花的钱)更新ans。所以空④天f[j]+total_b-total_b_prefix
空⑤,状态转移,如果可以在a点买第i件物品,可以从f[j-a[i]]转移过来,即去掉a店第i件物品价格时的最优解。

猜你喜欢

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