算法竞赛进阶指南-0x02 枚举、模拟、递推与递归

大家好这里是TKLA…初学算法的大一同学~现在已经系统学习过了C语言,正在学习C++以及python…希望能在CSDN上收获到很多知识!也希望自己能成为分享知识的一员!
目前学习的教材是这本 《算法竞赛进阶指南》(李煜东 著)


0X00 基本算法

这本书按照 0x加上两个16进制数字组成,还是比较有趣的


0x02 枚举、模拟、递推与递归

大概就是将题目意思直接用算式表达出来的方法


原书关于这部分的阐述…感觉比较基础…这里直接用两个例子来演示两者

对比:求阶乘

递推

typedef long long ll;
ll fic(int n){
     
	ll ans =  1;
	for(int i=2;i<=n;++i)
		ans *= i;
return ans;}

递归

typedef long long ll;
ll fic(int n){
     
	return n?n*fic(n-1):1;}

对比:斐波那契数列

递推

typedef long long ll;
ll fib(int n){
     
	ll a = 1,b = 1,c;
	for(int i=1;i<n;++i){
     
		c = a + b;
		a = b;
		b = c;}
return a;}

递归

typedef long long ll;
ll fib(int n){
     
	return (n<2)?1:fib(n-1)+fib(n-2);}

对比可以观察到以下几点:

区别 递推 递归
结构 循环结构 选择结构
代码量

然而事实上,复杂度与代码量的多少没有关系,更重要的是递归/递推方法(算法、实际问题),书中给到了几个应用模式。

简单应用

枚举形式 规模 一般遍历方式
多项式 nk 循环、递推
指数 kn 递归、位运算
排列 n ! 递归、next_permutation
组合 Cnm 递归+剪枝

0x02例题一:指数量枚举

指数量枚举

#include 
#include 
#include 
using namespace std;
int n;
vector<int> chosen;
void calc(int x){
     
    if(x == n+1){
     
        for(unsigned int i=0;i<chosen.size();++i)
            printf("%d ",chosen[i]);
        puts("");
        return;}
    calc(x+1);
    chosen.push_back(x);
    calc(x+1);
    chosen.pop_back();}
int main(void){
     
    cin >> n;
    calc(1);
return 0;}

0x02例题二:指数量枚举

组合量枚举
只多了一句而已

#include 
#include 
#include 
using namespace std;
int n,m;
vector<int> chosen;
void calc(int x){
     
    if(chosen.size() > m||chosen.size()+(n-x+1)<m)
        return;
    if(x == n+1){
     
        for(unsigned int i=0;i<chosen.size();++i)
            printf("%d ",chosen[i]);
        puts("");
        return;}
    chosen.push_back(x);
    calc(x+1);//注意顺序
    chosen.pop_back();
    calc(x+1);}
int main(void){
     
    cin >> n >> m;
    calc(1);
return 0;}

0x02例题三:指数量枚举

排列量枚举

#include 
#include //当然是使用全排列啦~
using namespace std;
const int MAX_N=11;
int n,a[MAX_N];
int main(void){
     
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        a[i]=i;
    do{
     
        for(int i=1;i<=n;++i)
            cout << a[i] << " ";
        cout << endl;
    }while(next_permutation(a+1,a+n+1));
return 0;}

0x02例题四:费解的开关

费解的开关
这道题和POJ熄灯问题题干是一样的,所以我直接写POJ这道题解答就可以了叭…

此题大致意思是说,按下一个开关同时会使得毗邻位点开关也响应,目标熄灭所有的灯。

1.每个开关最多按一次可使得最终方法数最小
2.开关顺序与结果无关
3.熄灭一行灯可以通过熄灭下一行的灯实现
Sample Input
2
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0
0 0 1 0 1 0
1 0 1 0 1 1
0 0 1 0 1 1
1 0 1 1 0 0
0 1 0 1 0 0
Sample Output输出操作方案,1表示按此处的开关
PUZZLE #1
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0
PUZZLE #2
1 0 0 1 1 1
1 1 0 0 0 0
0 0 0 1 0 0
1 1 0 1 0 1
1 0 1 1 0 1

emm…首先嗷,采取枚举即可

  • 按列枚举,这样就少了一层循环。
  • 构建 6*8 的矩阵…减少边界情况的判断
#include 
using namespace std;
int p[6][8],press[6][8];
bool guess(){
     
	int c,r;
	for(r=1;r<5;++r)
		for(c=1;c<7;++c)
			press[r+1][c] = (p[r][c]+press[r][c]+press[r-1][c]+press[r][c-1]+press[r][c+1])%2;
	for(c=1;c<7;++c)
		if((press[5][c]+press[4][c]+press[5][c-1]+press[5][c+1])%2 != p[5][c])
			return false;//能否全熄灭
return true;}
void enumrate(){
     
	int c;
	for(c=1;c<7;++c)
		press[1][c] = 0;
	while(!guess()){
     
		++press[1][1];
		c = 1;
		while(press[1][c]>1){
     
			press[1][c] = 0;
			++c;
			++press[1][c];/*进位*/}}
return;}
int main(void){
     
int cases;
scanf("%d",&cases);
for(int r=0;r<6;++r)
	press[r][0] = press[r][7] = 0;
for(int c=1;c<7;++c)
	press[0][c] = 0;
for(int i=0;i<cases;++i){
     
	for(int r=1;r<6;++r)
		for(int c=1;c<7;++c)
			scanf("%d",&p[r][c]);
	enumrate();
	printf("PUZZLE #%d\n",i+1);//这里有空格的,不写就会PE
	for(int r=1;r<6;++r){
     
		for(int c=1;c<7;++c)
			printf("%d ",press[r][c]);
		printf("\n");}}
return 0;}

没用递归是不是跑题了

0x02例题五:四柱汉诺塔

复习:基本汉诺塔
An = 2 An-1 + 1根据不动点原理,x = -1,两边减去x,得 An = 2n - 1
复习:汉诺双塔
洛谷此题
显然,An = 2n+1 - 2

POJ此题
An = { 1,i=1; min{2Ai-k+2k-1},else}
关于怎么理解这个式子…用一个图来解释下叭!算法竞赛进阶指南-0x02 枚举、模拟、递推与递归_第1张图片

分治

分形

栈理论

你可能感兴趣的:(算法竞赛进阶指南,算法)