读算法竞赛入门

读算法竞赛入门

经典算法与思想

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的表达式,其中aj为09,如果穷举abcde和fghij则需要10!次,但如果将原式写成abcde=n*fghij,只穷举fghij再判断abcde是否满足要求则可以使穷举次数减少到不到10000次。

最大连续和问题

  • 给出一个实数序列,A1,A2,......An,求最大连续和 ;这里有一个思想可以降低算法的时间复杂度:连续子序列之和等于两个前缀和之差

最一般的解法就是穷举法,从A1开始进行穷举,算法的复杂度是O(n3);但先求前缀和可以使算法的复杂度降低为O(n2)。

posted @ 2017-02-26 08:57  jiahu  阅读(415)  评论(0编辑  收藏  举报