读算法竞赛入门
经典算法与思想
TIPs
- 在代码中获得圆周率pi的比较好的方式是使用库math.h中的三角函数:4.0atan(1.0),4.0acos(1.0)等
- sacanf的返回值是成功输入的变量的个数,例如如果
scanf("%d%d",&a,&b)
成功执行则返回2。 #ifdef LOCAL...
,freopen("test.in","r",stdin);
,freopen("test.out","w",stdout)
- 有些问题的输出不会溢出,但其运算过程很可能会发生溢出,所以这点需要注意。
- 在linux系统中栈大小没有保存在可执行文件中,不过可以使用指令ulimit来改变栈的大小;在windows系统中栈大小保存在可执行文件中,使用gcc编译时可以使用指令
-Wl,--stack=16777216
来指定栈的大小。所以一般将较大的数组保存在main函数之外或者使用堆中的空间。
使用递归逆序输出数组
代码:
#include<stdio.h>
#include<string.h>
void reverse_str(const char*str){
int len = strlen(str);
if(len == 0){
//在基准情形中执行的一些动作没有“重复性”
return;
}
else{
reverse_str(str+1);
putchar(*(str));//“递归”之后需要做一些事情
}
}
int main(){
char str[] = "abcdefg";
reverse_str(str);
}
aabb完全平方数(p17)
注意floor在程序中的作用
#include<stdio.h>
#include<math.h>
int main(){
for(int i=1;i<10;i++){
for(int j=0;j<10;j++){
int a = i*1100 + j*11;
//int b = sqrt(a); //如果这里b是int型则floor的使用就没有意义了
double b = sqrt(a);
if(floor(b+0.5) * floor(b+0.5) == a)printf("%d\n",a);
}
}
}
开关灯问题 uva 10110
直接明了的方式是使用数组模拟这些动作,但效率不好。这些灯的开关是有规律的,开关被触发奇数次则电灯是开着的,如果被触发偶数次则是关着的,奇数偶数只与电灯编号因数个数的奇偶性有关。
一个整数的因数必然是成对出现的,例如15=1*15、15=3*5;9=1*9、9=3*3。可以发现只有完全平方数的因数是奇数个,只要判断电灯编号是否是完全平方数就可以判断电灯的状态。
注意:
当前题目中n的取值范围是2^32-1则必须使用无符号int,因为long在很多平台上与int取值范围相同,所以使用long时也必须是无符号的。
打印蛇形数
右上角的初始值、循环内的两种判断、内存空间的初始化
注意事项:在堆中分配的内存一般需要使用memset主动设置为0,否则可能都是乱码;
最长回文词 uva 11151
核心思想是枚举,但枚举也是有方向的:向右,或向两侧
竖式问题(p87)
一般而言没有效率要求的问题上,怎样简单怎样求解。
相同问题的统一处理。
问题:找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的数字集合
解法:
注意函数的使用方法:sprintf(buffer,"%d%d%d%d%d",a,b,c,d,e);strchar(string,char)
总结:
一些相同的操作可以合并然后做统一的处理,例如本问题中的“子集”问题,这些字符串合并之后再进行处理可以简化编码。
素数求解问题
使用i*i = x 来判断x是否是素数是有问题的,因为当x很大时i*i可能溢出:
比例说当判断2147395601是否是素数时,i需要大于46340,可46340*46340将溢出。
相对比较简单且不易溢出的方法:
int is_prime(int x){
assert(x>0);
//int prime = 1; //多此一举
if(x == 1) return 0;
else{
int m = floor(sqrt(x) + 0.5); //必须加0.5
for(int i = 2;i<m;i++){
if(x%i == 0){
//prime = 0;
//break; //直接返回0即可
return 0;
}
}
}
//return prime; //直接返回1即可
return 1;
}
WERTYU uva 10082
使用常量数组有时可以简化逻辑
善用常数字符串将简化操作(p94),isdigit(int) ,isprint(int), isalpha(int) toupper(int) tolower() in ctype.h
#include<stdio.h>
char s[55]="`1234567890-=QWERTYUIOP[]\ASDFGHJKL;'ZXCVBNM,./";
int main()
{
int i,c;
while((c=getchar())!=EOF)
{
//直接遍历,获得 i,这样可以简化逻辑(不用写for循环体)
for(i=1;s[i]&&s[i]!=c;i++);
if(s[i]) putchar(s[i-1]); //字符串的结尾标志是'\0'
else putchar(c);
}
return 0;
}
周期串 uva 455
首先要注意的是 对0取余可能使程序假死,所以 m%0
使不能使用的,这点编译与执行的过程中都不会报错,但程序的执行结果是错误的
m%n
可以看做是 m - (int)(m/n)*n
,前提是m和n均为正整数
小学生算术 uva10035
注意进位
#include<stdio.h>
int main(){
int a,b;
int c = 0;
int ans = 0;
while(1){
scanf("%d%d",&a,&b);
if(a == 0 && b == 0)break;
c = 0;
for(int i = 0;i<9 && a>0 && b>0;i++){
//c = (a%10 + b%10 + c)/10 ;
c = (a%10 + b%10 + c)>9?1:0;//判断要快于除法
ans += c;
a = a/10;b = b/10;
}
if(ans == 0)
printf("No carry operation.\n");
else{
printf("%d carry operations.\n",ans);
}
}
}
阶乘的精确值 uva623
n<=500,打印n!的精确值
字母重排
如何确定两个单词的组成相同?是迭代判断?显然效率太低,最好的办法是先排序这两个单词,然后再比较排序后的字符串。
C标准库函数qsort:
void qsort( void *buf, //指向数据
size_t num, //需要比较num项数据
size_t size, //每项数据的大小
int (*compare)(const void *, const void *) );
//对于compare的要求,第一项大于第二项则返回正值;等于返回0;小余返回负值。
cantor数表 uva264
首先确定位置.判断在第几斜列有两种思想:
- 迭代法,迭代n:1+2+...+n来判断m在第n列
- 用数学法判断:n<=1/2 *k(k+1),通过解不等式来获得n,这样更快
猜数字游戏(理解的不是很透彻)p99
循环列表
如何实现字符串数组的循环访问(使用整数取余运算)
生成元问题
查表思想、也许运算的结果不会溢出,但运算的中间值可能溢出,这样依旧会出错
TEX中的引号
注意事项:getchar的返回值为int型,因为EOF是int型;逐字符读取的函数有getchar(),int fgetc(FILE *stream)
参考代码:
// UVa272 Tex Quotes
// Rujia Liu
#include<stdio.h>
int main() {
int c, q = 1;
while((c = getchar()) != EOF) {
if(c == '"') { printf("%s", q ? "``" : "''"); q = !q; }
else printf("%c", c);
}
return 0;
}
回文词与镜像词
- 注意:
* 首先问题是如何判断回文与镜像
Circular Sequence
- 注意两个字符串以字典序比较大小的方法,容易出的逻辑错误是只比大小不判相等。
for(int i = 0; i < n; i++)
if(s[(p+i)%n] != s[(q+i)%n])
return s[(p+i)%n] < s[(q+i)%n];
UVA 679 Dropping Balls
- 两种典型的思想:“枚举”,递归思想
思想篇
暴力求解
暴力求解也有一定的技巧,例如给定n,求abcde/fghij=n的表达式,其中a~j为0~9,如果穷举abcde和fghij则需要10!次,但如果将原式写成abcde=n*fghij,只穷举fghij再判断abcde是否满足要求则可以使穷举次数减少到不到10000次。
最大连续和问题
给出一个实数序列,A1,A2,......An,求最大连续和 ;这里有一个思想可以降低算法的时间复杂度:连续子序列之和等于两个前缀和之差
最一般的解法就是穷举法,从A1开始进行穷举,算法的复杂度是O(n^3);但先求前缀和可以使算法的复杂度降低为O(n^2)。