• 1.1 教学计划与递归



    1.1 教学计划与递归




    由数据结构反推算法复杂度以及算法内容

    作者: yxc

    一般 ACM 或者笔试题的时间限制是\(1\)秒或\(2\)秒。
    在这种情况下,C++ 代码中的操作次数控制在\(10^7 \sim 10^8\)为最佳。

    下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:

    1.\(n \leqslant 30\):指数级别、DFS+剪枝、状态压缩DP;
    2.\(n \leqslant 100 \Rightarrow O(n^3)\):Floyd、DP、高斯消元;
    3.\(n \leqslant 1000 \Rightarrow O(n^2),\; O(n^2logn)\):DP、二分、朴素版Dijkstra、朴素版Prim、Bellman-Ford;
    4.\(n \leqslant 10000 \Rightarrow O(n\sqrt n)\):块状链表、分块、莫队;
    5.\(n \leqslant 100000 \Rightarrow O(nlogn)\):各种sort、线段树、树状数组、set/map、heap、拓扑排序、Dijkstra+heap、Prim+heap、SPFA、求凸包、求半平面交、二分、CDQ分治、整体二分;
    6.\(n \leqslant 1000000 \Rightarrow O(n),\;\)以及常数较小的\(O(nlogn)\)算法:单调队列、hash、双指针扫描、并查集、KMP、AC自动机;常数较小的\(O(nlogn)\)的做法:sort、树状数组、heap、Dijkstra、SPFA;
    7.\(n \leqslant 10000000 \Rightarrow O(n)\):双指针扫描,KMP、AC自动机、线性筛素数;
    8.\(n \leqslant 10^9 \Rightarrow O(\sqrt n)\):判断质数;
    9.\(n \leqslant 10^{18} \Rightarrow O(logn)\):最大公约数,快速幂;
    10.\(n \leqslant 10^{1000} \Rightarrow O((logn)^2)\):高精度加减乘除;
    11.\(n \leqslant 10^{100000} \Rightarrow O(logk \times loglogk)\)\(k\)表示位数:高精度加减、FFT/NTT;

    注:这里的\(log\)指的是以\(2\)为底的对数;






    递归

    int f(int n){
    	f(n-1);
    }
    
    • 递归即自己调用自己

    例:斐波那契数列

    \(f=\{1,2,3,5,8,cdots\}\)

    \(n=1 \quad f_1=1\)\(n=2 \quad f_2=2\)

    $f_n=f_{n-1}+f_{n-2} \quad n \geqslant 3 $

    int f(int n){
    	if(n==1) return 1;
    	if(n==2) return 2;
    	return f(n-1)+f(n-2);
    }
    

    递归\(\Rightarrow\)递归搜索树






    92. 递归实现指数型枚举


    \(1\sim n\)\(n\) 个整数中随机选取任意多个,输出所有可能的选择方案。


    输入格式

    输入一个整数 \(n\)


    输出格式

    每行输出一种方案。

    同一行内的数必须升序排列,相邻两个数用恰好 \(1\) 个空格隔开。

    对于没有选任何数的方案,输出空行。

    本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。


    数据范围

    \(1\leqslant n \leqslant 15\)


    输入样例

    3
    

    输出样例

    
    3
    2
    2 3
    1
    1 3
    1 2
    1 2 3
    

    难度: 简单
    时/空限制: 5s / 256MB
    来源: 《算法竞赛进阶指南》
    算法标签:递归



    思路

    • 数据范围为\(1\leqslant n \leqslant 15\),所以可以用时间复杂度为\(O(2^n)\)的算法来做;

    • 对于\(1\sim n\)\(n\)个数,每个数有 选/不选 两种情况,所以总共的方案数即为\(2^n\),故总的时间复杂度为\(O(n 2^n)\)

    • 递归(即DFS),最重要的是顺序,即找一个顺序,可以把所有方案不重不漏地找出来;

    • \(1 \sim n\),依此考虑每个数 选/不选;

    例:\(n=3\)时的递归搜索树

    • 状态(即每个数 选/不选)可以开一个长度为\(n\)的数组来记录;

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int N=16;
    int n;
    int st[N];//状态,记录每个位置当前的状态:0表示还没考虑,1表示选它,2表示不选它
    void dfs(int u){//u代表当前枚举到第u位
        if(u>n){
            for(int i=1;i<=n;++i)
            	if(st[i]==1)
                	printf("%d ",i);
    		printf("\n");
            return;
        }
    	st[u]=2;
    	dfs(u+1); //第一个分支:不选
    	st[u]=0; //恢复现场
    	
    	st[u]=1;
    	dfs(u+1); //第二个分支:选
    	st[u]=0; //恢复现场
    }
    int main(){
    	cin>>n;
    	dfs(1);
    	return 0;
    }
    /*
    运行时间: 32 ms
    运行空间: 856 KB 
    */
    

    如果要将方案记录下来

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int N=16;
    int n;
    int st[N];
    vector<vector<int>> ways;
    //ways代表方案
    void dfs(int u){
        if(u>n){
    		vector<int>way;
    		for(int i=1;i<=n;++i) //记录方案
    			if(st[i]==1)
    				way.push_back(i);
    		ways.push_back(way);
            return;
        }
    	st[u]=2;
    	dfs(u+1); 
    	st[u]=0; 
    	
    	st[u]=1;
    	dfs(u+1); 
    	st[u]=0;
    }
    int main(){
    	cin>>n;
    	dfs(1);
    	for(int i=0;i<ways.size();++i){
    		for(int j=0;j<ways[i].size();++j) printf("%d ",ways[i][j]);
    		puts("");
    	}
    	return 0;
    }
    /*
    运行时间: 89 ms
    运行空间: 3084 KB 
    */
    






    94. 递归实现排列型枚举


    \(1\sim n\)\(n\) 个整数排成一行后随机打乱顺序,输出所有可能的次序。


    输入格式

    一个整数 \(n\)


    输出格式

    按照从小到大的顺序输出所有方案,每行 \(1\)个。

    首先,同一行相邻两个数用一个空格隔开。

    其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。


    数据范围

    \(1 \leqslant n \leqslant 9\)


    输入样例

    3
    

    输出样例

    1 2 3
    1 3 2
    2 1 3
    2 3 1
    3 1 2
    3 2 1
    

    难度: 简单
    时/空限制: 5s / 256MB
    来源: 《算法竞赛进阶指南》
    算法标签:递归



    思路

    • 数据范围为\(1 \leqslant n \leqslant 9\)\(9!=326880\),因此时间复杂度大约为\(O(n \times n!) \Rightarrow DFS\)

    字典序:

    \(A:a_1,a_2,\cdots,a_n\)

    \(B:b_1,b_2,\cdots,b_m\)

    \(a_i<b_i\)\(a_i\)不存在但\(b_i\)存在 \(\quad\Rightarrow A<B\)

    \(a_i>b_i\)\(b_i\)不存在但\(a_i\)存在 \(\quad\Rightarrow A>B\)

    \(n=m\)并且\(a_n=b_m \quad\Rightarrow A=B\)

    • 全排列问题一般有两种枚举方式:

      1.依此枚举每个数放哪个位置;

      2.依此枚举每个位置放哪个数;

    2.的例:\(n=3\)时的递归搜索树

    上图保证了(相对意义上的)左子树的方案 字典序一定小于 右子树的方案;

    • 开一个长度为\(n\)的数组来记录状态;

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int N=10;
    int n;
    int state[N];// 0 表示还没放数,1~n表示放了哪个数
    bool used[N];//true表示用过,false表示还未用过
    void dfs(int u){
    	if(u>n){//边界
    		for(int i=1;i<=n;++i) printf("%d ",state[i]); //打印方案
    		puts("");
    		return;
    	}
    	//依次枚举每个分支,即当前位置可以填哪些数
    	for(int i=1;i<=n;++i)
    		if(!used[i]){
    			state[u]=i;
    			used[i]=true;
    			dfs(u+1);
    			//恢复现场
    			state[u]=0;
    			used[i]=false;
    		}
    }
    int main(){
    	scanf("%d",&n);
    	dfs(1);
    	return 0;
    }
    /*
    运行时间: 373 ms
    运行空间: 7000 KB 
    */
    

    分析时间复杂度

    • 需要递归\(n\)层;
    • 第一层时间复杂度为\(O(n)\);(一个for循环)
    • 第二层时间复杂度为\(O(n\times n)\);(由第一层衍生出\(n\)个分支,每个分支一个for循环)
    • 第三层时间复杂度为\(O(n \times n-1 \times n)\);(由第二层衍生出\(n-1\)个分支)
    • \(\cdots\)
    • 倒数第二层时间复杂度为\(O(n! \times n)\)
    • 最后一层时间复杂度为\(O(n! \times n)\);(最后一层有\(n!\)个结点,且需要输出方案)

    总的时间复杂度为\(O[n(1+n+n(n-1)+n(n-1)(n-2)+ \cdots + n!)]\)

    相当于\(P_n^0+P_n^1+P_n^2+\cdots +P_n^n \geqslant n!\)

    原式相当于:

    \[n(n!+\frac{n!}{1}+\frac{n!}{1 \times 2} +\frac{n!}{1 \times 2 \times 3}+\cdots \frac{n!}{(n-1)!}) \]

    对其进行放缩:

    \[原始\leqslant n(n!+\frac{n!}{1}+\frac{n!}{2} +\frac{n!}{4}+\frac{n!}{8}\cdots \frac{n!}{(n-1)!}) \leqslant n \times n!(1+1+\frac{1}{2}+\frac{1}{4}+\frac{1}{8}+\cdots)\leqslant 3n! \]

    故整个时间复杂度小于等于\(O(n \times n!)\)






    作业

    • AcWing 93.递归实现组合型枚举

    • AcWing 1209.带分数

  • 相关阅读:
    背包九讲
    hdu 2602 Bone Collector(01背包)
    hdu 1176 免费馅饼(类似于hdu 2084 数塔那道题目 )
    hdu 1114 PiggyBank(完全背包)
    hdu 2084 数塔
    hdu 1058 Humble Numbers【丑数】
    hdu 1114 PiggyBank【完全背包】
    hdu 2602 Bone Collector (01背包经典入门)
    hdu 2602 Bone Collector【01背包】
    【Python】python的各种函数
  • 原文地址:https://www.cnblogs.com/Potrem/p/AcWing_lanqiao_1.html
Copyright © 2020-2023  润新知