航空航天大类C语言程设第三次上机练习

航空航天大类C语言程设第三次上机练习


第三期更新,是第三次上机的内容(由于第三次练习赛每个题都有写头,这次就更这么多 说白了还是博主太懒)。

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 0h1,h223,0m1,m259,且 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(1w105)(单位吨),还好他记得货物的重量模3、5、7的余数即 w % 3 、 w % 5 、 w % 7 w\%3、w\%5、w\%7 w%3w%5w%7,但是他不知道如何通过余数求出体重,你能帮帮他吗?

多组测试数据
每组数据一行三个自然数(包括零的那种自然数)用空格隔开,分别小于3、5、7,表示 w % 3 、 w % 5 、 w % 7 w\%3、w\%5、w\%7 w%3w%5w%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} ax(mod3)ay(mod5)az(mod7)
x , y , z x,y,z x,y,z是每组数据读入的三个 w % 3 、 w % 5 、 w % 7 w\%3、w\%5、w\%7 w%3w%5w%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} xa1(modm1)xa2(modm2)...xan(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 n1个数的乘积,令 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} Miti1(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} ap11(modp),也就是 a ⋅ a p − 2 ≡ 1 ( m o d p ) a\sdot a^{p-2}\equiv1\pmod{p} aap21(modp)会惊奇地发现模 p p p以后, a p − 2 ≡ a − 1 ( m o d p ) a^{p-2} \equiv a^{-1}\pmod{p} ap2a1(modp),就是 a a a的数论倒数,当然数字给定容易求出 3 5 3 − 2 ≡ 2 ( m o d 3 ) 35^{3-2}\equiv2\pmod{3} 35322(mod3) 2 1 3 − 2 ≡ 1 ( m o d 5 ) 21^{3-2}\equiv1\pmod{5} 21321(mod5) 1 5 7 − 2 ≡ 1 ( m o d 7 ) 15^{7-2}\equiv1\pmod{7} 15721(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} (knkn1...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=a2kna2kn1...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 1n的排列。定义区间最大值 M ( i , j ) = max ⁡ i ≤ k ≤ j { a k } M(i,j)=\max_{i≤k≤j}\{a_k\} M(i,j)=maxikj{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 nt+1种选择,答案自然就是 t ( n − t + 1 ) t(n-t+1) t(nt+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,2321]所以可能得开这样一个长的数组,显然这个方法是不好了,题目也说了这题数据很大,用数组会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;
}

你可能感兴趣的:(C语言)