选择题
- 在一条长度为 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
:
【解析】数学期望解题技巧:
- 确定取值:写出 X X X可能取的全部值
- 求概率:求 X X X每个值的概率
- 写出 X X X的分布列
- 由均值的定义求出 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 的个数,代码如下:
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
。
- 2−3 树是一种特殊的树,它满足两个条件:
1)每个内部结点有两个或三个子结点;
2)所有的叶结点到根的路径长度相同。
如果一棵 2-3 树有 10 个叶结点,那么它可能有 ( ) 个非叶结点。
【解析】符合上述条件的2−3 树,非叶结点的个数有8
和7
两类,例如:
问题求解
方程 a ∗ b = ( a o r b ) ∗ ( a a n d b ) a*b =(a\ or\ b) * (a\ and\ b) a∗b=(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) a∗b=(a or b)∗(a and b)的 a 、 b a、b a、b有如下关系:一个数的二进制中为
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)×(3∣7)=7×3=21。
根据 a a a中1
的情况进行分类:
有0个1, b b b有 2 5 2^5 25种方案, a 和 b a和b a和b方案有 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 a和b方案有 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 a和b有 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 a和b有 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 a和b方案有 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 a和b方案有 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 = 0
,v[0] = true, v[7] = true, v[8] = true
,cnt = 1
i = 1
,v[1] = true
,cnt = 2
i = 2
,v[2] = true, v[4] = true
,cnt = 3
i = 3
,v[3] = true
,cnt = 4
i = 4
i = 5
,v[5] = true
,cnt = 5
i = 6
,v[6] = true, v[9] = true
,cnt = 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 6
、1 5 3 2 6 4
、1 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 到 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 1≤n≤105。
#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]
。
- 一只小猪要买 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
件物品价格时的最优解。