目录
P63_习题4-1_组合数
为什么m = n-m
P64_习题4-3_素数判定
为什么要floor
到底为什么判断到sqrt(n)即可
++和*
*t=*a
for循环你真的门儿清吗
为什么要把较大的数组放在main函数外
P82_eg4-3_救济金发放_UVa133
P63_习题4-1_组合数
防止溢出,又因为m <= n,所以用n!/m!=(m+1) (m+2)…(n-1)n
long long C(int n, int m) {
if(m < n-m) m = n-m;
long long ans = 1;
for(int i = m+1; i <= n; i++) ans *= i;
for(int i = 1; i <= n-m; i++) ans /= i;
return ans;
}
为什么m = n-m
然后还要知道到底为什么要把n!/m!给约分。因为我们这里,n肯定是最大的数,要防止n!的溢出。约分之后这个值就变成了从m+1到n的累乘。
而我们知道
这是一件好事。着说明我们可以把m的值变成我们想要的。
我们需要从m+1到n的累乘,那一定是m越大计算的次数就越小。
那么,这个小和大怎么来分辨呢。就是用if(m < n-m)。
P64_习题4-3_素数判定
素数判定。编写函数,参数是一个正整数n,如果它是素数,返回1,否则返回0。
int isPrime(int n){
if(n == 1) return 0;
for(int i = 2;i <= sqrt(n);i++){
if(n % i == 0) return 0;
}
return 1;
}
int is_prime(int n)
{
if(n == 1) return 0;
int m = floor(sqrt(n) + 0.5);
for(int i = 2; i <= m; i++)
if(n % i == 0) return 0;
return 1;
}
为什么要floor
到底为什么判断到sqrt(n)即可
仔细思考就会发现,其实数字x的因数分成两大部分,一部分是小于x的平方根,另外一部分大于x的平方根,小于平方根和大于平方根的部分是一一对应的,因而可以只判断从2到平方根的数字是否都能被整除即可。
++和*
*t=*a
void swap(int* a, int* b)
{
int *t;
*t = *a; *a = *b; *b = *t;
}
这个程序错在哪里?t是一个指向int型的指针,因此*t是一个整数。用一个整数作为辅助 变量去交换两个整数有何不妥?事实上,如果用这个函数去替换程序4-6,很可能会得到“4 3”的正确结果。为什么笔者要坚持说它是错误的呢? 问题在于,t存储的地址是什么?也就是说t指向哪里?因为t是一个变量(指针也是一个 变量,只不过类型是“指针”),所以根据规则,它在赋值之前是不确定的。如果这个“不确 定的值”所代表的内存单元恰好是能写入的,那么这段程序将正常工作;但如果它是只读 的,程序可能会崩溃。读者可尝试赋初值int *t = 0,看看内存地址“0”能不能写。
for循环你真的门儿清吗
for循环中的最后一个语句是最后执行的,也就是说无论写成先自加还是后自加都是一个效果
为什么要把较大的数组放在main函数外
局部变量也是放在堆栈段的。栈溢出不一定是递归调用太多,也可能是 局部变量太大。只要总大小超过了允许的范围,就会产生栈溢出。
P82_eg4-3_救济金发放_UVa133
n(n<20)个人站成一圈,逆时针编号为1~n。有两个官员,A从1开始逆时针数,B从n开
始顺时针数。在每一轮中,官员A数k个就停下来,官员B数m个就停下来(注意有可能两个
官员停在同一个人上)。接下来被官员选中的人(1个或者2个)离开队伍。输入n,k,m
输出每轮里被选中的人的编号(如果有两个人,先输出被A选中的)。例
如,n=10,k=4,m=3,输出为4 8, 9 5, 3 1, 2 6, 10, 7。注意:输出的每个数应当恰好占3列。
【分析】
仍然采用自顶向下的方法编写程序。用一个数组表示人站成的圈。为了避免
人走之后移动数组元素,用0表示离开队伍的人,数数时跳过即可。
注意go这个函数。
当然也可以写两个函数:逆时针go和顺时针go,但是仔细思考后发现这两个函数可以合并:
逆时针和顺时针数数的唯一区别只是下标是加1还是减1。把这个+1/-
1抽象为“步长”参数,就可以把两个go统一了
#include<stdio.h>
#define maxn 25
int n, k, m, a[maxn];
//逆时针走t步,步长是d(-1表示顺时针走),返回新位置
int go(int p, int d, int t) {
while(t--) {
do { p = (p+d + n-1) % n + 1; } while(a[p] == 0); //走到下一个非0数字
}
return p;
}
int main() {
while(scanf("%d%d%d", &n, &k, &m) == 3 && n) {
for(int i = 1; i <= n; i++) a[i] = i;
int left = n; //还剩下的人数
int p1 = n, p2 = 1;//p1为A,-1是顺时针
while(left) {
p1 = go(p1, 1, k);
p2 = go(p2, -1, m);
printf("%3d", p1); left--;
if(p2 != p1) { printf("%3d", p2); left--; }
a[p1] = a[p2] = 0;
if(left) printf(",");
}
printf("\n");
}
return 0;
}
注意go这个函数。当然也可以写两个函数:逆时针go和顺时针go,但是仔细思考后发现这两个函数可以合并:逆时针和顺时针数数的唯一区别只是下标是加1还是减1。把这个+1/- 1抽象为“步长”参数,就可以把两个go统一了。
int go(int p, int d, int t) {
while(t--) {
do { p = (p+d + n-1) % n + 1; } while(a[p] == 0); //走到下一个非0数字
}
return p;
}
感觉本题最难的地方就是这个go函数。作者的抽象能力确实很强,能把两件事(逆时针和顺时针)抽象出一个模型然后统一写在一个函数中。