Q1(上机A题)有一个小于 2 32 2^{32} 232的正整数。他发现这个数在现在的绝大多数机器上可以用一个32位的二进制数表示(不足32位用0补足)。他称这个二进制数的前16位为“高位”,后16位为“低位”。将它的高低位交换,可以得到一个新的数。他想知道这个新的数是多少(用十进制表示)。
例如,数1314520用二进制表示为0000 0000 0001 0100 0000 1110 1101 1000(添加了11个前导0补足为32位),其中前16位为高位,即0000 0000 0001 0100;后16位为低位,即0000 1110 1101 1000。将它的高低位进行交换,我们得到了一个新的二进制数0000 1110 1101 1000 0000 0000 0001 0100。它即是十进制的249036820。
输入一个小于 2 32 2^{32} 232的正整数,输出交换高低位后的整数(这里高低位交换是对unsigned操作)
注意第一点,用unsigned int
的好处在于范围在0~4294967296所以保证不会溢出(至于为什么考虑溢出后面再说),当然读入方式就不一样,用scanf("%u",&n)
方法读入
然后是怎么做,题目的例子已经提示的很明显了,一个数的后16位如何到前面去和前16位如何到后面去,肯定会涉及到位运算的移位操作,观察例子的二进制表示,一个数,退16位,前16位全为0,后16位是之前的16位,进16位,前16位由后面的16位顶上而后16位全为0,这两个应该可以通过一种操作并为一个数,那么答案只有一个了,就是做或运算了,两个数或运算,比如0000 0000 0000 0000 0000 0000 0001 0100和0000 1110 1101 1000 0000 0000 0000 0000或运算就是0000 1110 1101 1000 0000 0000 0001 0100
代码
#include
int main(){
unsigned int n;
scanf("%u", &n);
printf("%u", (n >> 16)|(n << 16));
return 0;
}
Q2(上机C题)
一场比赛将开始于 h 1 : m 1 h1:m1 h1:m1,结束于 h 2 : m 2 h2:m2 h2:m2,现在需要求出这场比赛的中间时刻
输入:
第一行包含两个整数 h 1 , m 1 h1,m1 h1,m1,第二行包含两个整数 h 2 , m 2 h2,m2 h2,m2
保证 0 ≤ h 1 , h 2 ≤ 23 , 0 ≤ m 1 , m 2 ≤ 59 0 \le h1,h2\le23,0\le m1,m2\le59 0≤h1,h2≤23,0≤m1,m2≤59,且 m 1 % 2 = m 2 % 2 m1 \%2 = m2 \% 2 m1%2=m2%2。
保证开始时间和结束时间在同一天且开始时间早于结束时间。
输出:两个整数 h 3 , m 3 h3,m3 h3,m3表示比赛的中点时间。
注意时间必须是xx:xx的格式!比如 01:02
由于分钟对2同余,所以可以保证中间时刻分钟是整数,求法也很简单,两个时间相对0时0分过去的分钟数取平均(相当于求中点),再化成xx:xx的形式就是中间时刻了,也注意到注意事项了吧,小时和分钟都得输出两位,对于小于10的数要注意了,当然熟悉格式化输出就没有问题,介绍一下printf("%02d", n)
的格式化输出,意思是输出一个整数,必须是两位,不足的用0补前位,是不是一下子就好了呢ヽ( ̄▽ ̄)ノ
#include
int main() {
int h1, m1, h2, m2, h3, m3, t;
scanf("%d:%d", &h1, &m1);
scanf("%d:%d", &h2, &m2);
t = ((h2 * 60 + m2) + (h1 * 60 + m1)) / 2;
printf("%02d:%02d", t / 60, t % 60);
return 0;
}
Q3(上机E题)货物重量
有一批货要被运送,但是负责联系的人忘记货物重多少。他只记得货物的重量为 w ( 1 ≤ w ≤ 105 ) w(1\le w\le105) w(1≤w≤105)(单位吨),还好他记得货物的重量模3、5、7的余数即 w % 3 、 w % 5 、 w % 7 w\%3、w\%5、w\%7 w%3、w%5、w%7,但是他不知道如何通过余数求出体重,你能帮帮他吗?
多组测试数据
每组数据一行三个自然数(包括零的那种自然数)用空格隔开,分别小于3、5、7,表示 w % 3 、 w % 5 、 w % 7 w\%3、w\%5、w\%7 w%3、w%5、w%7。
对于每组数据,输出一行一个整数 w w w。
多组测试数据指的是数据组数不一定,需要持续读入直到EOF
(End Of File)
第一种比较好像的处理方法,鉴于数据范围是1到105,自然可以遍历验证余数是不是满足条件(这里暂时省去主函数)
int weight(int x, int y, int z) {
int p, w;
for (p = 1; p <= 105; p++) {
if (p % 3 == x && p % 5 == y && p % 7 == z) {
w = p;
break;
}
}
return w;
}
当然这个题肯定是有更好的做法,这其实是一个典型的同余方程
{ a ≡ x ( m o d 3 ) a ≡ y ( m o d 5 ) a ≡ z ( m o d 7 ) \begin{cases} a\equiv x\pmod{3}\\ a\equiv y\pmod{5}\\ a\equiv z\pmod{7}\\ \end{cases} ⎩⎪⎨⎪⎧a≡x(mod3)a≡y(mod5)a≡z(mod7)
x , y , z x,y,z x,y,z是每组数据读入的三个 w % 3 、 w % 5 、 w % 7 w\%3、w\%5、w\%7 w%3、w%5、w%7,幸运的是模3、5、7互质,所以可以用中国剩余定理,且方程组一定有解,可以这样构造方程组的解:
一般地对于这样一个同余方程组:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) . . . x ≡ a n ( m o d m n ) \begin{cases} x\equiv a_1\pmod{m_1}\\ x\equiv a_2\pmod{m_2}\\ ...\\ x\equiv a_n\pmod{m_n}\\ \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(modm1)x≡a2(modm2)...x≡an(modmn)
只要 m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn互质,则方程组一定有解
令 M = m 1 × m 2 × . . . × m n = ∏ i = 1 n m i M=m_1\times m_2 \times ... \times m_{n}= \prod_{i=1}^{n}m_{i} M=m1×m2×...×mn=∏i=1nmi(是不是看出105是个什么名堂了)
然后令 M i = M m i M_i = \frac{M}{m_{i}} Mi=miM是除了 m i m_i mi以外其他 n − 1 n-1 n−1个数的乘积,令 t i t_i ti为 M i M_i Mi模 m i m_i mi的数论倒数(即满足 M i t i ≡ 1 ( m o d m i ) M_it_i\equiv 1\pmod{m_i} Miti≡1(modmi)),令 x = a 1 t 1 M 1 + a 2 t 2 M 2 + . . . + a n t n M n = ∑ i = 1 n a i t i M i x=a_1t_1M_1+a_2t_2M_2+...+a_nt_nM_n=\sum_{i=1}^{n}a_it_iM_i x=a1t1M1+a2t2M2+...+antnMn=∑i=1naitiMi,则为方程组在模 M M M下的解,一般地通解是 x = k M + ∑ i = 1 n a i t i M i x=kM+\sum_{i=1}^{n}a_it_iM_i x=kM+∑i=1naitiMi(证明略(。・ω・。))
对于本题,由于模都是给定的,可以直接求数论倒数,即求35,21,15的数论倒数,由于数论倒数不是唯一的,虽然手算不难算出,但还是用稍微正规点的办法,这里幸好3、5、7是素数可以用费马小定理的方法求数论倒数
费马小定理:
对于正整数 a a a和素数 p p p,有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv1\pmod{p} ap−1≡1(modp),也就是 a ⋅ a p − 2 ≡ 1 ( m o d p ) a\sdot a^{p-2}\equiv1\pmod{p} a⋅ap−2≡1(modp)会惊奇地发现模 p p p以后, a p − 2 ≡ a − 1 ( m o d p ) a^{p-2} \equiv a^{-1}\pmod{p} ap−2≡a−1(modp),就是 a a a的数论倒数,当然数字给定容易求出 3 5 3 − 2 ≡ 2 ( m o d 3 ) 35^{3-2}\equiv2\pmod{3} 353−2≡2(mod3), 2 1 3 − 2 ≡ 1 ( m o d 5 ) 21^{3-2}\equiv1\pmod{5} 213−2≡1(mod5), 1 5 7 − 2 ≡ 1 ( m o d 7 ) 15^{7-2}\equiv1\pmod{7} 157−2≡1(mod7),然后通解就可以表达为 70 x + 21 y + 15 z ( m o d 105 ) 70x+21y+15z\pmod{105} 70x+21y+15z(mod105)了,注意到如果同余后是0,说明原来的重量是105。
函数体:
int weight(int x, int y, int z) {
int w;
w = (70 * x + 21 * y + 15 * z) % 105;
w = w ? w : 105;
return w;
}
附上一个快速幂方法求方幂取模的方法(数论倒数(number-theoretic reciprocal)),当然如果代码中全程不取模就是一种从 O ( n ) O(n) O(n)优化到 O ( log n ) O(\log{n}) O(logn)的算方幂的算法
typedef long long LL
LL ntr(LL a, LL p) {
LL ans = 1;
LL d = p - 2;
while (d) {
if (d & 1) {
ans = (ans * a) % p;
d >>= 1
}
a = (a * a) % p;
}
return ans;
}
先说求方幂,原理就是求 a d a^d ad时,假设 d d d的二进制表示是 ( k n k n − 1 . . . k 0 ) 2 (k_{n}k_{n-1}...k_0)_{2} (knkn−1...k0)2,则 a d = a 2 k n a 2 k n − 1 . . . a 2 k 0 a^d=a^{2^{k_n}} a^{2^{k_{n-1}}}...a^{2^{k_{0}}} ad=a2kna2kn−1...a2k0,所以那步if (d & 1)
就是判断 d d d的二进制表示是否含某个 k i k_i ki,举个例子求 1 5 5 15^5 155时,结果初始为1,5的二进制表示是101,末尾有1,结果乘以15,15变成15的平方;5退一位是2,末尾不是1,不乘,15的平方变成15的四次方,2退一位变成1,末尾是1,结果乘以15的四次方, 1 5 5 = 15 × 1 5 4 15^5=15\times15^4 155=15×154。也许这里会觉得快速幂的方法不明显,如果方幂足够大时就能体现出差距了。
取模则利用基本性质 a × b ( m o d p ) = a ( m o d p ) × b ( m o d p ) a\times b\pmod{p} =a\pmod{p}\times b\pmod{p} a×b(modp)=a(modp)×b(modp),在每一步满足if (d & 1)
那里取模,顺便 a a a平方时也顺便取模。
Q4(上机F题)
定义 a n a_n an 为 一个 1 ∼ n 1∼n 1∼n的排列。定义区间最大值 M ( i , j ) = max i ≤ k ≤ j { a k } M(i,j)=\max_{i≤k≤j}\{a_k\} M(i,j)=maxi≤k≤j{ak}
问有多少个区间的最大值为 n n n?
输入:
第一行,一个整数n
第二行,n个整数,表示一个排列。
输出
一个整数,表示符合要求的区间个数。
输入样例:
4
1 2 4 3
输出样例:
6
样例解释
共有 [ 1 , 3 ] , [ 1 , 4 ] , [ 2 , 3 ] , [ 2 , 4 ] , [ 3 , 3 ] , [ 3 , 4 ] [1,3],[1,4],[2,3],[2,4],[3,3],[3,4] [1,3],[1,4],[2,3],[2,4],[3,3],[3,4] 6个区间的最大值为4
题意理解一下就是找出含有这个数组最大值的闭区间个数,其实这个题本质上是个数学题,答案由最大值在数列的位置决定,比如说有 n n n个数,最大值在第 t t t个位置,区间左端点只有 1 , 2 , . . . , t 1,2,...,t 1,2,...,t共 t t t个选择,右端点从 t , . . . , n t,...,n t,...,n(从样例可知区间可以只含一个数)有 n − t + 1 n-t+1 n−t+1种选择,答案自然就是 t ( n − t + 1 ) t(n-t+1) t(n−t+1)了,而要编程解决的问题就是确定最大值所在位置,遍历一遍就行了。
#include
int main() {
int n, i;
scanf("%d", &n);
int m[n];
for (i = 0; i < n; i++) scanf("%d", &m[i]);
int max = m[0], t; #最大值初始为数列第一个数,遍历整个数列,如果有更大的值则更新顺便更新所在位置
for (i = 1; i < n; i++) {
if (m[i] > max) max = m[i];
}
for (i = 0; i < n; i++) {
if (m[i] == max) {
t = i;
break;
}
}
t += 1;
int N = t * (n - t + 1);
printf("%d", N);
return 0;
}
Q5(上机G题)
题目描述
有 n n n个箩筐,编号是 1 1 1~ n n n。
一开始所有的筐内都是空的,随后会进行两种操作:
操作1:往编号为 x x x的筐内投 k k k份草稿
操作2:把所有筐内的草稿给集中到第 n n n个筐内
输入:
第一个数为箩筐的总数 n n n(保证 n n n小于等于100)
第二个数为总的操作数 m m m
随后的 m m m行,每行有三个数 o , x , k o,x,k o,x,k
如果 o o o等于1,代表进行操作1,往编号 x x x筐内投 k k k份草稿
如果o等于2,代表进行操作2,此时 x x x和 k k k的值无用
(数据保证此题运算中不会发生int
溢出)
输出:
输出一行,用空格隔开的 n n n个数,代表最终 1 1 1~ n n n筐的内的草稿数
思路很基本,就是直接模拟题意,开一个数组存储各个框内的草稿数
#include
int b[100] = {0};
int main() {
int n, m, i, j;
scanf("%d %d", &n, &m); #读入操作次数和框的总数
int o, x, k;
for (i = 0; i < m; i++) {
scanf("%d %d %d", &o, &x, &k);
if (o == 1) b[x - 1] += k;
else if (o == 2) { #操作2,遍历将前面的草稿加到最后一个框再将前面的框清0
for (j = 0; j < n - 1; j++) {
b[n - 1] += b[j];
b[j] = 0;
}
}
}
for (i = 0; i < n; i++) {
printf("%d ", b[i]);
}
return 0;
}
Q6(上机H题)
题目描述
摸鱼村要选村长了!
选村长的规则是村里每个人进行一次投票,票数大于人数一半的成为村长。
然鹅摸鱼村的人都比较懒,你能帮他们写一个程序来找出谁当选村长吗?
(每名村民的编号都是一个int
范围内的整数)
输入
多行,每行一个数字(int
范围内)
输出
输出出现次数超过总数一半的数字。(保证一定有解)
样例输入:
1
1
12345678
2
1
输出:
1
保证有解代表某个编号出现次数一定超过总次数 n n n的一半,即为 [ n 2 ] + 1 [\frac{n}{2}]+1 [2n]+1次,有一种思路就是开一个数组,有一票就直接下标位置的数加1,但是问题来了,比如样例,编号可以很大,意味着这个数组的长度要足够大,int
范围是 [ − 2 32 , 2 32 − 1 ] [-2^{32},2^{32}-1] [−232,232−1]所以可能得开这样一个长的数组,显然这个方法是不好了,题目也说了这题数据很大,用数组会REG(Runtime Error)哦,所以只能用巧妙的方法。
而这个巧方法是什么呢?由于是逐一EOF
式读入,可以这么想,定义最初编号的出现次数,如果下一个读到相同编号则次数加1,如果不一样则减1,直到减为0,再计下一个读入的数的出现次数……拿样例来讲,“出现次数”初始为0,最初读入1,次数加1;下一个是1,再加1;下一个不是1,减1;再下一个不是1,减1,为0了,结束计数,下一个读入的是1,次数为1;读入完毕,最终结果是1“出现”了1次,而这个编号就是选出的编号,是不是很神奇啊?
原理是这样的,就是抵消法,一个编号如果出现次数没有过半(包括恰好一半)一定能跟其他编号抵消,只有出现超过半数的编号才抵消不完。
#include
int main() {
int n, x = 0, y = 0;
while (~scanf("%d", &n)) {
if (!y) {
x = n;
y++;
}
else {
if (n == x) y++;
else y--;
}
}
printf("%d", x);
return 0;
}