1.语法复习
1.1输入和输出
格式字符,指定宽度,左对齐和右对齐
1.2字符串处理
1.2.1字符串处理函数
字符串连接
strcat(str1,str2)
复制
strcpy(str1,str2)
strncpy(str1,str2,n)指定str2中的前n个
将字符串中的大小写转换
strlwr(字符串),strupr(字符串)
比较
strcmp(str1,str2),strncmp(str1,str2,n)只比较前n个
查字典,若str1大(在字典更后面),返回值>0,相等=0,str2大<0
1.3运算符优先级
1.4文件
1.5一些小点
if()
else if()
else if()
为嵌套
强制转换是取有效数位,不是四舍五入
数字运算的整数和小数
2.算法复习和常见题型处理
2.1循环语句枚举
是个好东西,特别是在强规律性下的情况,在不限制算法复杂度的情况下非常方便,需要注意的点是要明确各个量表示的是啥,同时注意边界条件
2.2递推算法
有时候不太容易想到要用递推,可以考虑把想递推的优先度提前。对递推算法也是需要注意边界条件,即递推的初始情况
2.3打印图形
需要注意的点是最好直接写成嵌套的if else if else…否则容易不出现空格的时候多打空格。同时循环嵌套不一定是直接for{for{}},有的时候不如for{if{for}else{for}}好。打印图形要么人找规律,要么让机器++找规律(或者递推),难找规律时就给机器做,不用想出(i,j)处的图形
3.期末做的题的总结
3.1给整数编号
问题描述:
使用以下结构
struct nn
{
int no;
int num;
};
建立一个结构数组,从键盘输入若干个整数,保存在数组元素的num成员中,并根据输入的数按从小到大进行编号,将编号保存在no成员中。按整数的输入顺序输出整数及其编号。输入的整数不超过100个,每个整数的绝对值不大于1000,输入时以空格分隔整数。
例如输入:4 3 6 8 2 3 9
输出:
4 4
3 2
6 5
8 6
2 1
3 3
9 7
要求:当两个整数相等时,整数的排列顺序由输入的先后次序决定。例如:输入的第2个整数为3,第6个整数也为3,则将先输入的整数3的编号在前,后输入的整数3的编号在后。编写函数完成为整数编号。
函数原型:int number( char *str, struct nn a[] );
其中:str:保存以字符串方式接收的键盘输入,
a: 保存整数及编号的结构数组的首地址,
函数返回值:已输入的整数的个数。
预设代码
前置代码
view plainprint?
/* PRESET CODE BEGIN - NEVER TOUCH CODE BELOW */
#include <stdio.h>
struct nn
{ int no;
int num;
};
typedef struct nn DATA;
int number( char * , DATA []);
int main( )
{
DATA b[100];
char sa[500];
int i, n;
gets( sa );
n = number( sa, b );
for ( i=0; i<n; i++ )
printf("%d %d\n", b[i].num, b[i].no );
return 0;
}
/* Here is waiting for you
int number( char * str, DATA a[] )
{
....
}
*/
#include <stdio.h>
struct nn
{ int no;
int num;
};
typedef struct nn DATA;
int number( char * , DATA []);
int main( )
{
DATA b[100];
char sa[500];
int i, n;
gets( sa );
n = number( sa, b );
for ( i=0; i<n; i++ )
printf("%d %d\n", b[i].num, b[i].no );
return 0;
}
int number( char * str, DATA a[] )
{
int n=0,j=0;
while(str[j]!='\0'){
j++;
if(str[j]==' ')
n++;
}
n++;
//先把符号转成数字,m代表数位
int N[100],m=0,i=0,t=0; //注意这里一定要赋初值,乐学上没有默认int为0
char *p1=str,*p2=str;
while(i<n){
while(*(p2+1)!=' '&&(*(p2+1)!='\0')){ //注意这个地方是与!!开始写成或了一直是bug
p2++;
}
m=p2-p1;
N[i]=(int)*p1-48;
for(t=1;t<=m;t++){
N[i]=N[i]*10+(int)*(p1+t)-48;
}
if(*(p2+1)!='\0')
p1=p2+2;
p2++;
i++;
}
for(i=0;i<n;i++){
a[i].num=N[i];
a[i].no=0;
}
//找最小的,用k[100]记录重复的位号,i记录为第几个
int min=1001,k[100],k1,No=1;
while(No<=n){
min=1001,k1=0;
for(j=0;j<100;j++){
k[j]=0;
}
for(i=0;i<n;i++){ //扫描,找到目前最小的数,k[k1]记录该数在哪里,k1代表个数,保证了输入的先后顺序
if(N[i]<min){
min=N[i];
k1=1; //找到更小的就清空;
for(j=0;j<100;j++){
k[j]=0;
}
k[0]=i;
}
else if(N[i]==min){ //我是大蠢猪,这个地方赋值之后就一样了,需要else if
k[k1]=i;
k1++;
}
} //扫描完成后,将找到的几个min输入,同时将他们赋值1002
for(i=0;i<k1;i++){
a[k[i]].no=No;
No++;
N[k[i]]=1002;
}
}
return n;
}
要点总结
- 关于输入数组:可以循环输入,或者字符串形式输入,gets()最后是’\0’,注意处理方法,这里第一次写还是想了一会的,是套路。我用的是滑动窗口,用任意形式的储存也可以。开始用滑动窗口时p2忘往下移动了,是死循环
- 关于两个bug的修正花了很长时间:一个是注意与或(等于A或等于B的否定是不等于A且不等于B),一个是int在乐学上跑要赋初值,不会默认为0,会是无效内存引用
- 关于程序优化:不需要定义N[100],因为开始不知道怎么跳过已经编号的数,看了网上其他人写的程序后知道原来可以通过定义下限,将已编号的值赋给下限,之后要求大于下限即可,程序如下
for(i=1;i<=n;i++)
{
flag=1;
for(k=0;k<n;k++)
if(a[k].num<min&&a[k].num>bottom)
{ deposit=k;min=a[k].num;}
else if(a[k].num==bottom&&k>store) //不在上一轮的位置存
{ flag=0;deposit=k;break;}
if(flag) bottom=min;
a[deposit].no=i;min=1000;store=deposit; //store存的是上一个数的位置
}
相当于这个第一次只能找到一个最小数,就进入下一个循环了,但是可以在之后的循环中找到;i即编号
就很好,写得比我的清楚很多
3.2找循环节
问题描述:
对于任意的真分数 N/M ( 0 < N < M ),均可以求出对应的小数。如果采用链表存储各位小数,对于循环节采用循环链表表示,则所有分数均可以表示为如下链表形式。
输入: N M
输出: 整个循环节
要求:
编写一个尽可能高效的查找循环节起始点的函数: NODE * find( NODE * head, int * n ) 。函数的返回值为循环节的起点(即图中的指针p),n为循环节的长度。
说明:提交程序时请同时提交将分数转换为小数的函数 change( int n, int m, NODE * head ) 。
前置代码
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{ int data;
struct node * next;
} NODE;
NODE * find( NODE * , int * );
void outputring( NODE * );
void change( int , int , NODE * );
void outputring( NODE * pring )
{ NODE * p;
p = pring;
if ( p == NULL )
printf("NULL");
else
do
{ printf("%d", p->data);
p = p->next;
} while ( p != pring );
printf("\n");
return;
}
int main()
{ int n, m;
NODE * head, * pring;
scanf("%d%d", &n, &m);
head = (NODE *)malloc( sizeof(NODE) );
head->next = NULL;
head->data = -1;
change( n, m, head );
pring = find( head, &n );
printf("ring=%d\n", n);
outputring( pring );
return 0;
}
/* Here is waiting for you.
void change( int n, int m, NODE * head )
{
}
NODE * find( NODE * head, int * n )
{
}
*/
/* PRESET CODE END - NEVER TOUCH CODE ABOVE */
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{ int data;
struct node * next;
} NODE;
NODE * find( NODE * , int * );
void outputring( NODE * );
void change( int , int , NODE * );
void outputring( NODE * pring )
{ NODE * p;
p = pring;
if ( p == NULL )
printf("NULL");
else
do
{ printf("%d", p->data);
p = p->next;
} while ( p != pring );
printf("\n");
return;
}
int main()
{ int n, m;
NODE * head, * pring;
scanf("%d%d", &n, &m);
head = (NODE *)malloc( sizeof(NODE) );
head->next = NULL;
head->data = -1;
change( n, m, head );
pring = find( head, &n );
printf("ring=%d\n", n);
outputring( pring );
return 0;
}
//思路:模拟计算无线循环小数的做法,当出现与之前相同的余数时则可判定是最小循环节
void change( int n, int m, NODE * head )
{
int rm[1000];//储存余数;(余数英语remainder)
int pl=0;//记录算到小数点后第几位了
int begin;//记录开始循环在小数点后第几位
int infinite=1;//判断是否为无限循环小数
int i=0;//储存余数的顺序
NODE *p1=head,*p2=head;
rm[0]=n;
while(1){
n*=10;
i++;
p2=(NODE *)malloc(sizeof(NODE));
p1->next=p2;
p2->next=NULL;
p2->data=n/m;
p1=p2;
if(n%m==0){
infinite=0;
break;
}
//检验是否停止
int flag=0;
for(int j=0;j<i;j++){
if(rm[j]==n%m){
flag=1;
begin=j+1;
break;
}
}
if(flag)
break;//但是不知道是从哪一位开始的循环,开始没有定义pl记录算到小数点的第几位了;
rm[i]=n%m;
n%=m;
}
//这里直接将head->data存储为初始位置
if(infinite==0){
head->next=NULL;
return ;
}
else{
head->data=begin;
}
}
NODE * find( NODE *head, int *m ){
if(head->next==NULL){
*m=0;
return NULL;
}
int pl=head->data;
NODE *p1=head,*p2;
while(pl--){
p1=p1->next;
}
p2=p1;
*m=1;
while(p2->next!=NULL){
(*m)++;
p2=p2->next;
}
p2->next=p1;
return p1;
}
要点总结
- 开始试图从数学角度直接找出循环节的长度,并试图说明N,M互质时循环节长度与N=1时一致。由于数学水平不够,我没有证明出来(感觉上是合理的,但严格说明很困难),于是只有模拟算循环小数的方法,余数与若之前出现过则开始循环。感觉编程就是把一个理论计算不了或者难于理论计算的东西靠大量相似重复计算做出来,程序AC之后我验证了初始的猜想,随便试了十几个数,是对的。比较好奇的是关于任意1/M的循环节长度有没有数学计算式?
- debug太痛苦了,以后每一步尽量写一下注释,不然找起来很困难。对于死循环多半是忘++了
3.3猜数字看人品
题目描述:
描述
Tom 和 Jerry 做猜数字的游戏,Tom 想一个数字然后让 Jerry 去猜,数字的范围在 1 到 10 之间。对于 Jerry 每讲的一个数,Tom 都要讲这个数是 too high 或者 too low 或者 right on,直到 right on 结束。为了防止 Tom 作弊,Jerry 把每一次的对话记录下来,现在让你去判断 Tom 有没有作弊。
输入
游戏可能做很多次,直到 Jerry 猜 0 的时候游戏结束,每一次猜测由一个正整数和一行回答组成。
输出
对每一次游戏如果 Tom 的回答有自相矛盾的地方,就输出 Tom is dishonest,否则输出 Tom may be honest。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//思路:输入,判断是否一轮结束,或者整体结束,只用储存每一轮的字符串(定义结构体)
typedef struct{
int cai;//记录猜的数字
int fw; //记录高了还是低了
} JL;
int main(){
JL s[200];
char str1[3],str2[10];
while(1){
gets(str1);
if(str1[0]=='0')
break;
if(str1[0]=='1'&&str1[1]=='0')
s[0].cai=10;
else
s[0].cai=str1[0]-48;
int flag;//1为真,0为假
int m=0;//记录是说的第几次
//循环输入不判断,并记下answer为s[m].cai
while(1){
gets(str2);
if(str2[0]=='t'){
if(str2[4]=='h')
s[m++].fw=1;
else
s[m++].fw=0;
}
else{
break;
}
gets(str1);
if(str1[0]=='1'&&str1[1]=='0')
s[m].cai=10;
else
s[m].cai=str1[0]-48;
}
//现在写判断有无说慌
flag=1;
for(int i=0;i<m;i++){
if(s[i].cai>s[m].cai&&(s[i].fw==1)||s[i].cai<s[m].cai&&s[i].fw==0)
continue;
else
flag=0;
}
if(flag)
printf("Tom may be honest\n");
else
printf("Tom is dishonest\n");
}
return 0;
}
要点总结
- 像这种循环输入的不是一次性输完,一般就是while条件break,这样也合理,就像用一个app打开,输入输出,然后关闭。亏我以前全部是用n记录输入的个数再输出,蠢
- 一个傻逼错误找了将近俩小时,判断输入是否是“10”的时候写成str2了。其实完全不用字符串,可以直接输入数字的
- 最近结构体练多了全在用结构,快是快,但空间复杂度略大。这里没必要保存每一句话,只需要缩范围即可,动态规划才是合理的(比较大侠小的次数一样,但用到的空间更小)。看到的别人写的优秀程序
#include<stdio.h>
#include<string.h>
int main()
{
int n,high=11,low=-1;
char s[10];
while(scanf("%d",&n))
{
if(n==0) break;
getchar();
gets(s);
if(strcmp(s,"too high") == 0)
{
if(n < high)
{
high = n;
}
}
else if(strcmp(s,"too low") == 0)
{
if(n > low)
{
low = n;
}
}
else if(strcmp(s,"right on") == 0)
{
if((n>low)&&(n<high))
{
printf("Tom may be honest\n");
}
else
{
printf("Tom is dishonest\n");
}
high = 11;
low = -1;
}
}
}
虽然这个地方没必要用比较字符串的函数,但是考虑到程序的可移植性问题,我的这种只比较特殊字符的做法就不如用函数合适。
3.4水王争霸
Description
众所周知,联盟有很多水王,他们的发贴数是如此之多,以至于必须要用高精度数才能保存。
为了迎接国庆,联盟决定举行一次水王争霸赛,比赛的规则是将这些水王截止到2030年9月30日23时59分59秒这一刻所发的总贴数从大到小进行排序。每个水王当然都想取得尽量靠前的名次,所以他们竭尽全力,不择手段地进行灌水。
终于,激动人心的一刻到来了,2030年10月1日0时0分0秒,你作为裁判得到了每个水王的发贴数,现在,你的任务是公正地把这些水王按照发贴数从大到小进行排序。
Input
输入的第一行是一个1到1000的整数N,表示总共有N位水王参加了争霸赛。
以下依次给出每位水王的描述,一位水王的描述占据两行,第一行为一个仅由字母和数字组成的长度不超过20的字符串,代表这个水王的ID,第二行一个高精度的整数(非负数),代表这个水王的发贴数。注意,这个整数的首位没有不必要的0。
所有水王发贴数数字的总长度(注意,是总长度而不是总和)不会超过10000。除了子母、数字和必要的换行,输入中不会出现空格等字符。
Output
依次输出按照发贴数从大到小排好序的各位水王的ID,每个ID占据单独的一行。不能有任何多余的字符。若几个ID的发贴数相同,则按照ID的字典顺序先后排列。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//定义结构体,ID,发帖总数sum,sum长度n
typedef struct shui{
char ID[20];
int n;
char *sum;
} SHUI;
SHUI sh[1000];
//比较谁该在前面,前面的“大”,1右大,否则0
int judge(SHUI i, SHUI j){
if(i.n<j.n)
return 1;
if(i.n>j.n)
return 0;
for(int m=0;m<i.n;m++){
if(i.sum[m]<j.sum[m])
return 1;
if(i.sum[m]>j.sum[m])
return 0;
}
if(strcmp(i.ID,j.ID)>0)
return 1;
if(strcmp(i.ID,j.ID)<0)
return 0;
}
void sort(SHUI sh[],int n)
{
int i,j;
for(i=0;i<n;i++)
{
for(j=0;j<n-i-1;j++)
{
if(judge(sh[j],sh[j+1]))
{
SHUI t;
t=sh[j];
sh[j]=sh[j+1];
sh[j+1]=t;
}
}
}
}
int main()
{
int n,i;
scanf("%d",&n);
getchar();
for(i=0;i<n;i++)
{
scanf("%s",sh[i].ID);
getchar();
char str[10000];
gets(str);
sh[i].sum=(char *)(malloc(strlen(str)*sizeof(char)));
strncpy(sh[i].sum,str,strlen(str));
sh[i].n=strlen(str);
}
sort(sh,n);
for(i=0;i<n;i++){
printf("%s\n",sh[i].ID);
}
return 0;
}
要点总结
- 当内存不够时,动态分配内存(malloc),注意分配方法