在刚刚开始准备学习些算法之前,网络上很多算法进阶攻略都会建议先从一些简单的基本算法着手入门,其中包括“枚举,贪心,递归和分治,递推,构造,模拟”这些,正好这段时间我在看的刘汝佳的《算法竞赛入门经典》这本书中涉及了很多关于基础的东西,对于算法的学习和ACM的训练都很有帮助,所以我打算从“蓝书”入手,学习一些基本的算法与解题技巧(前辈们的建议也大致如此),之后再开始《算法导论》的学习。
在博客里贴一些学习的笔记和代码,一方面是为了巩固自己的学习,加深理解和记忆,另一方面也能更好地督促自己不偷懒!还有,在刚开始学习的时候会经常上网查找一些解题报告,发下很多博主贴了许多连编译都不能通过的代码,真是害人害己,所以贴一些自己写过的正确的代码,也意在不浪费后来人的时间,大家共同学习,共同进步。
首先就是暴力算法中的“枚举”。
在枚举复杂对象之前,可以先尝试从枚举一些相对简单的东西入手,例如整数,子串等等,尽管暴力枚举不用太动脑筋,但实现对问题进行一些简单的分析会让问题得到更好的解决。
7.1.1除法
输入正整数n,按从小到大的顺序输出所有形如abcde/fghij = n的表达式,其中a~j恰好为数字0~9的一个排列,2<=n<=79.
样例输入:
62
样例输出:
79546/01283 = 62
94736/01528 = 62
/*7.1.1除法*/
#include <stdio.h>
int is_different(int x, int y);
int main()
{
int n, B, tot, x, y;
int res=1, count=0, bit[20];
while(scanf("%d", &n)==1)
{
for(B = 1234; B<=98765; ++B)
{
if(B*n>=12345&&B*n<=98765&&is_different(B,B*n))
{
printf("%d/",B*n);
if(B<10000)
printf("0");
printf("%d=%d\n", B, n);
}
}
}
getchar();
return 0;
}
int is_different(int x, int y)
{
int bit1[10], bit2[10], count1=0, count2=0;
int i, j;
count1 = 0;
count2 = 0;
while(x>0)
{
bit1[count1++] = x % 10;
x = x/10;
}
while(y>0)
{
bit2[count2++] = y % 10;
y = y/10;
}
for(i = 0; i < count1; ++i)
for(j = i+1; j < count1; ++j)
if(bit1[i] == bit1[j])
return 0;
for(i = 0; i < count2; ++i)
for(j = i+1; j < count2; ++j)
if(bit2[i] == bit2[j])
return 0;
for(i = 0; i < count1; ++i)
for(j = 0; j < count2; ++j)
if(bit1[i] == bit2[j])
return 0;
if(count1==4)
for(i = 0; i < count2; ++i)
if(bit2[i]==0)
return 0;
if(count2==4)
for(i = 0; i < count1; ++i)
if(bit1[i]==0)
return 0;
return 1;
}
这道题看上去只是需要暴力枚举一下就可以了,但是10!的枚举量可不是开玩笑的,所以根据题意,我们只需要枚举出fghij就可以算出abcde,枚举量就降低到不到1万,所以即使是用暴力枚举这种没脑子的方法,也是需要分析问题的。
7.1.2最大乘积
输入n个元素组成的序列5,你需要找出一个乘积最大的连续子序列。如果这个最大乘积不是正数,应输出-1(表示无解)。1≤n≤18,-10≤Si≤10.
样例输入:
3
2 4 -3
5
2 5 -1 2 -1
样例输出:
8
20
#include <stdio.h>
int main()
{
int n, i, pro, head, rear, s[20];
long long max;/*最大的乘积也不会超过10^18,所以用long long存放*/
while(scanf("%d", &n)==1)
{
for(i = 1; i<=n; ++i)
scanf("%d", &s[i]);
max = s[1];/*先把序列的头元素赋值给与乘积比较的最大值max*/
for(head = 1; head<=n; ++head)/*枚举子串起点head*/
for(rear = 1; head<=rear && rear<=n; ++rear)
{/*枚举子串终点rear并且限制rear≤head,避免出错*/
pro = 1; /*初始化乘积pro为1*/
for(i = head; i<=rear; ++i)
{
pro *= s[i];
if(pro > max)
max = pro;
}
}
printf("%d\n", max);
}
getchar();
return 0;
}
最先看到题目的时候没有想出系统的方法来把所有的子序列一一进行处理,后来看书下的分析才明白,既然是有序序列,只要枚举子序列的起点和终点即可,这也体现出系统枚举的意义。从这道题目开始,枚举的思路与简便(暴力)也开始渐渐有所体现。
另外子序列元素一共不超过18个,每个元素的大小也限定在绝对值为10以内,所以最大的乘积也不会超过10^18,所以乘积结果得用 long long 类型存储。
7.1.3 分数拆分
输入正整数k,找到所有的正整数x≥y,使得1/k = 1/x + 1/y。
样例输入:
2
12
样例输出:
2
1/2 = 1/6 + 1/3
1/2 = 1/4 + 1/4
8
1/12 = 1/156 + 1/13
1/12 = 1/84 + 1/14
1/12 = 1/60 + 1/15
1/12 = 1/48 + 1/16
1/12 = 1/36 + 1/18
1/12 = 1/30 + 1/20
1/12 = 1/28 + 1/21
1/12 = 1/24 + 1/24
#include <stdio.h>
int main()
{
int k, x, y;/*k为输入的数字,x,y分别为两个分母*/
while(scanf("%d", &k)==1)
{
for(y = k+1; y<=2*k; ++y)
{
x = (y*k)/(y-k);
if(x*y/(x+y)==k)
printf("1/%d=1/%d+1/%d\n",k, x, y);
}
}
getchar();
return 0;
}
咦?这和枚举有什么关系?哦,在判断枚举范围的时候,因为x≥y,所以1/y ≥ 1/x。所以1/k - 1/x ≤ 1/y,即y ≤ 2k,所以得到了y的枚举范围,只要通过y算出x即可,不必再枚举x的值,就像<7.1.1除法>那题一样。
7.1.4 双基回文数
如果一个正整数n至少在两个不同的进位制b1和b2下都是回文数
(2≤b1,b2≤10),则称n是双基回文数(注意回文数不能包含前导0)。
输入正整数S < 10^6,输出比S大的最小双基回文数。
样例输入:1600000
样例输出:1632995
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int is_huiwen(int obj)
{
char handled[500];
int i, bit, count = 0, res = 0;
for(bit = 2; bit <= 10; ++bit)
{
itoa(obj, handled, bit);
/*itoa()函数作用是把数字obj转制成bit进制然后存放
进handle数组里,需要声明包含<stdlib.h>头文件,
另外handled[]数组必须是char型*/
count = strlen(handled); /*把数组长度传给count*/
for(i = 0; i<count/2; ++i)
{
if(handled[i] != handled[count-i-1])
{
res--; break;
}
}
res++;
}
if(res>1)
return 1;
else
return 0;
}
int main()
{
int n, i, bit, count;
while(scanf("%d", &n)==1)
{
for(i = n; ; ++i)
{
if(is_huiwen(i)==1)
{
printf("%d\n", i);
break;
}
}
}
getchar();
return 0;
}
=_=那么这题就没什么思路好说的了,直接从S+1开始暴力枚举,再判断是不是双基回文数就行。
书上关于简单枚举的内容差不多就是这样,更深入的学习,可以从POJ1753和POJ2965入手。