西北工业大学NOJ详解(更新中)
写在前面
本文旨在分享有关NOJ题目的思路和做法以及注意事项(不保证我分享代码的简洁和高效),***不鼓励***同学们直接Copy代码。希望大家在读完本文后能够有所收获,也希望同学们能够对我其中的错误进行指正。发布顺序不固定。其中发布的代码均已确认AC。
小建议:推荐大家可以尝试使用Clion这个IDE,在纠正代码错误方面十分好用。以及Snipaste截图软件,可以把截图始终显示在屏幕最上方,不必再来回看题。
关于NOJ
NOJ是西北工业大学开设的C语言实验课的作业,同时也是上课的主要内容。60题AC是报名期末考试的条件。但个人觉得其中部分题目难度过于大了,而这部分难度不是体现在编程上,反而体现在数学上,这也是我个人想进行题目解析和分享的一个原因。
详解
001 Hello World
#include <stdio.h>
int main(void) {
printf("Hello World");
return 0;
}
printf函数用法(部分软件初始代码就是“Hello World”(笑))
002 A+B
#include <stdio.h>
int main(void) {
int a,b;
scanf(" %d %d",&a,&b);
printf("%d",a+b);
}
这题就是简单的输入和输出,入门题。没什么需要讲的。
003 数据类型大小及范围
#include <stdio.h>
#include <limits.h>
int main()
{
int i,a=0;
scanf(" %d",&i);
switch(i)
{
case(1):{
printf("%llu,%d,%d",sizeof(char),CHAR_MIN,CHAR_MAX);//sizeof返回类型为unsigned long long
break;
}
case(2):{
printf("%llu,%d,%d",sizeof(unsigned char),a,UCHAR_MAX);
break;
}
case(3):{
printf("%llu,%d,%d",sizeof(short),SHRT_MIN,SHRT_MAX);
break;
}
case(4):{
printf("%llu,%d,%d",sizeof(unsigned short),a,USHRT_MAX);
break;
}
case(5):{
printf("%llu,%d,%d",sizeof(int),INT_MIN,INT_MAX);
break;
}
case(6):{
printf("%llu,%d,%d",sizeof(unsigned int),a,UINT_MAX);
break;
}
case(7):{
printf("%llu,%ld,%ld",sizeof(long),LONG_MIN,LONG_MAX);
break;
}
case(8):{
printf("%llu,%d,%lu",sizeof(unsigned long),a,ULONG_MAX);
break;
}
case(9):{
printf("%llu,%lld,%lld",sizeof(long long),LLONG_MIN,LLONG_MAX);
break;
}
case(10):{
printf("%llu,%d,%llu",sizeof(unsigned long long),a,ULLONG_MAX);
break;
}
default:return 0;
}
return 0;
}
思路:c语言中<limits.h>头文件中存储着各字符类型的大小范围,可以通过调用这个头文件再加上sizeof()函数,通过switch…case语句就可以实现题目要求。注意:<limits.h>头文件中没有unsigned …类型的最小值,其最小值就是0。另外:sizeof()函数返回值类型为unsigned long long,虽然这里当成int应该也可以AC。
注意数据类型。例如:不要把ULLONG_MAX用%d输出,这样会无法得到正确结果。
004 平均值
#include <stdio.h>
int main(void) {
long long a,b;
scanf( " %lld %lld",&a,&b);
printf("%lld",(a+b)/2);
return 0;
}
注意:这题若将a,b类型定义为int,则会显示WA,将其定义为long long则是AC。这题只有这点需要特别提出。
005 进制转换
#include <stdio.h>
int main(void) {
int n;
scanf(" %d",&n);
printf("%X,%o",n,n);
return 0;
}
注意:其中十六进制中的字母为大写。
这题考察格式化输出十六进制和八进制,分别用%X和%o就可以实现十进制向十六进制和八进制的转换。(若是%x则输出十六进制的字母为小写)
006 浮点数输出
#include <stdio.h>
int main(void) {
long double n;
scanf(" %Lf",&n);//l是大写
printf("%.6Lf,%.2Lf,%.8Lf",n,n,n);
return 0;
}
考查格式化输出小数点后位数。
虽然若是考虑到精度问题,这里使用long double更为合适,但使用double也可以AC。其中注意:输出long double类型需要使用%Lf,l采用大写形式。
007 动态宽度输出
#include <stdio.h>
int main(void) {
int m,n,k=1;//k用来统计m的位数
scanf(" %d %d",&m,&n);
int cy=m;//复制m的值
while(cy/=10)
k++;
if(n<=k)
printf("%d",m);
else{
for(int i=0;i<(n-k);i++)
printf("0");
printf("%d",m);
}
return 0;
}
注意:这里没有提及的是,当m宽度大于或等于n时,输出m本身。
思路:先统计m的位数k(将其不断/10,直至为0,每次k+1),再将其与n进行比较,若是位数大于等于n,则输出m;否则,在前面输入n-k个0,再输出m。
008 计算地球上两点之间的距离
#include<stdio.h>
#include<math.h>
double hav(long double a);
int main()
{
double a1,a2,b1,b2,c,d,pi=3.14159265;//pi值要足够精确
scanf(" %lf %lf %lf %lf",&a1,&a2,&b1,&b2);
c=hav((b1-a1)/180*pi)+cos(a1/180*pi)*cos(b1/180*pi)*hav((b2-a2)/180*pi);
d=acos(1-2*c)*6371;
printf("%.4lfkm",d);
return 0;
}
double hav(long double a)
{
double b=(1-cos(a))/2;
return b;
}
注意:这题计算需要用弧度而不是角度,因此经度和纬度都需要先换算成相应的弧度,才能进行计算。其中π值需要自己设置,要尽量精确。
思路:
由
H
a
v
e
r
s
i
n
e
公
式
计
算
出
c
=
h
a
v
(
d
r
)
的
值
。
由
h
a
v
(
θ
)
=
1
−
c
o
s
(
θ
)
2
,
转
化
为
c
o
s
(
θ
)
=
1
−
2
h
a
v
(
θ
)
,
即
d
r
=
cos
−
1
(
1
−
2
c
)
则
d
=
r
cos
−
1
(
1
−
2
c
)
由Haversine公式计算出c=hav(\frac{d}{r})的值。\\由hav(\theta)=\frac{1-cos(\theta)}{2},转化为cos(\theta)=1-2hav(\theta),\\即\frac{d}{r}=\cos^{-1}(1-2c)\\则d=r\cos^{-1}(1-2c)
由Haversine公式计算出c=hav(rd)的值。由hav(θ)=21−cos(θ),转化为cos(θ)=1−2hav(θ),即rd=cos−1(1−2c)则d=rcos−1(1−2c)
072 【专业融合:动能】热能计算
#include <stdio.h>
int main(void) {
double ti,tf,m1,c1,m2,c2;
//ti为初始温度,tf为加热后温度,m1、m2分别为液体和容器质量,c1、c2为热能百分比
scanf(" %lf%lf%lf%lf%lf%lf",&ti,&tf,&m1,&c1,&m2,&c2);
double q1=c1*m1*(tf-+ti),q2=c2*m2*(tf-ti);
//q1、q2分别为液体和容器所需要的热量
printf("%.2lfkJ,%.2lf%%,%.2lf%%\n",(q1+q2)/1000.0,(q2/(q1+q2)),(q1/(q1+q2)));
return 0;
}
AC需要注意的是kJ,k小写,J大写。还有输出顺序,先容器,后液体。虽然是液体先被输入(笑)。
题目错误:热能百分比样例输出错误,加上了%却没有✖️100。但若是加上这步操作,则为WA。
073 成绩单
#include <stdio.h>
#include "stdlib.h"
#include "string.h"
struct tagStudent{//定义一个结构体,照着题目做就行
char id[11];
char name[31];
int score;
};
void sequence(struct tagStudent **ppST,int n);//排序函数,对成绩进行排序
int main(void) {
int n;
scanf(" %d",&n);
struct tagStudent **ppST=(struct tagStudent**)malloc(n*sizeof(struct tagStudent*));
//定义结构体双指针
for(int i=0;i<n;i++){ //共n次循环,写入数据,同时分配堆
ppST[i]=(struct tagStudent*) malloc(sizeof(struct tagStudent));
scanf(" %s %s %d",ppST[i]->id,ppST[i]->name,&ppST[i]->score);
}
sequence(ppST,n); //对其排序
for(int i=0;i<n;i++){//依次打印到屏幕
printf("%s %s %d\n",ppST[i]->id,ppST[i]->name,ppST[i]->score);
}
for(int i=0;i<n;i++){
free(ppST[i]);//依次释放堆
ppST[i]=NULL;//赋初值
}
free(ppST);
ppST=NULL;
return 0;
}
void sequence(struct tagStudent **ppST,int n){
struct tagStudent *temps;
int logEqual;//用来记录有几个数相等
int len= strlen(ppST[0]->id);//获取学号位数,后面比较学号会用到
for(int i=0;i<(n-1);i++){//依次比较分数,若后面大于前面,则交换
for(int j=i+1;j<n;j++){
if(ppST[i]->score<ppST[j]->score){//比较分数
temps=ppST[i];
ppST[i]=ppST[j];
ppST[j]=temps;
}
}
}
//已按照分数实现排序
for(int i=0;i<(n-1);i++){//依次比较前后分数是否相等
logEqual=1;
if(ppST[i]->score!=ppST[i+1]->score)//如果不相等直接进行下一次比较
continue;
for(int j=i+1;j<(n-1);j++){//相等则继续判断之后是否依然相等
if(ppST[j]->score==ppST[j+1]->score)
logEqual++;//计数有几个相等的分数
else break;//不相等则停止计数
}
int cy=i;//复制此时i的值
for(;i<(cy+logEqual);i++){//如上面算法相似,比较学号,后面比前面小则交换
for(int j=(i+1);j<(cy+logEqual+1);j++){
for(int m=0;m<len;m++){//比较第(i+1)个和第(j+1)的学号的大小,从大位到小位比较
if(ppST[i]->id[m]>ppST[j]->id[m]){//字符会转化为ASCII码序号进行比较
temps=ppST[i];
ppST[i]=ppST[j];
ppST[j]=temps;
break;//比较出一位即可不再比较
}
else if(ppST[i]->id[m]<ppST[j]->id[m])
break;//比较出一位即可不再比较
}
}
}
}
temps=NULL;//因为没有给temps分配堆,故不需要释放堆,若释放则会出现乱码
}
思路:先排成绩,再在成绩相同的结构体中排学号。其中定义结构体双指针,使得可以在之间进行地址的交换,从而实现交换数据。
注意:
1.在交换地址时所使用的中间指针,由于没有分配堆,不需要进行内存释放。
2.在进行比较的时候,不要忽略使用break。若是忽视其中一个break就会导致结果出错,因为会出现过多比较的情况。比如:学号之间比较时,只要有一位不同就可以停止比较了,否则会进行下面位次的比较,导致出错。
字符串比较大小:这题需要学号之间进行比较,而字符串之间比较大小可以采用每个字符依次比较。字符在比较的时候会转化为ASCII码序号进行比较。比如字符‘0’会转化为数字48,‘1’转化为49。因此可以比较。此外:可以不按照题目提示,将学号的类型定义为long long,应该也能行得通。