本文持续更新
Update date: 2021/10/6
算法系列文章
搜索算法:遍历与枚举
分治算法:修身,齐家,编算法!

笔记目录

  • 什么是枚举?
  • 什么时候用枚举?
  • 机器学习中的枚举
    • 特征选择(feature extraction)
    • 参数调整(parameter selection)
    • 信息检索(information retrieval)
  • 典型例题
    • 立方质数(暴力枚举)
      • 暴力枚举法总结
    • 分木棍(二分查找)
      • 二分查找总结
    • 李老师的Lucky Number (枚举+优化)
      • 暴力法求解
      • 简单优化求解
    • 李老师的暑假旅行(简单背包:二叉树,DFS)
      • 枚举二叉树路径求解
    • 移除石头(NOIP原题)
      • 二叉树枚举求解
      • 二分法优化求解
    • 数组元素和(组合优化)
      • 暴力搜索
      • 组合优化

什么是枚举?

枚举(enumerate),顾名思义,找出问题的可能解,然后一个一个地尝试。

什么时候用枚举?

简单而言,任何时候。借用老师的话:

  • It should be your first idea!(拿道题,没思路,就枚举)
  • It could need optimization!(过不了,找问题,再优化)
  • It would be your last solution !(回头看,枚举是最“差”的算法,但是也是解决问题最基本的方法)

机器学习中的枚举

特征选择(feature extraction)

有这么一句话在业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。

深度学习能在一定程度上解决自动特征组合以及交互的问题,但是现阶段的实际应用中仍然需要做一些特征设计以及显示的特征组合。在特征选择的过程中,枚举算法仍然是重要的:即尝试→评估→优化

参数调整(parameter selection)

本质上,参数的调整都是枚举。目前常见的枚举方法是:网格搜索法。常见的采样标尺是:指数采样

信息检索(information retrieval)

对于给定的Query,遍历系统中所有的document,形成检索结果。

典型例题

立方质数(暴力枚举)

题目
http://algo.bjtu.edu.cn/contest/115/problem/A
如果一个质数能被表示为三个不同的质数的和的形式,那么我们称它为立方质数。现在给你一个数n,判断它是不是立方质数。

输入数据:正整数n,n<=1000

输出数据:Yes或者No

样例输入

19

样例输出

Yes

思路
新建list,标记从1到n中,所有的质数。复杂度:O(n2)\mathcal{O(n^2)}O(n2)
新建list时或许用memset函数,把数组置为0;也可以逐个遍历:

# include <cstring> // 必须引入
memset(void *str, int c, size_t n) // 不要用0之外的数字作为c

具体代码如下:

int prime[1001];
memset(prime,0,1001)// 全部置为0
for (int i = 0;i<1001;i++) {prime[i]=1;// 全部置为1
}

遍历2到i,是否能整除;如果发现能的,就不是质数

    //int n=1000;int n;scanf("%d",&n);for (int i = 1; i < 1001; ++i) {for (int j = 2; j < i; ++j) {if (i % j == 0){prime[i] = 0; // not primebreak;}}}

把找到的one-hot,整理为numeric:

    list<int> a;for (int i=1;i<=n;i++){if (prime[i]){a.push_back(i);}}a.erase(a.begin()); // 1不是质数,用erase(loc)去除

三层循环筛选立方质数

    for(auto num1: a){for(auto num2:a){for (auto num3:a) {//printf("%d %d %d %d\n",n,num1,num2,num3);if ((num1+num2+num3 == n) && (num1!=num2) && (num2!= num3) && (num1 !=num3) && (prime[n]==1)){printf("Yes");return 0;}}}}printf("No");return 0;
}

暴力枚举法总结

纯遍历的题目不难,重要的是细心、读懂题干。不要漏掉任何一个细节!!!

分木棍(二分查找)

题目
我们有n根的木棍。现在从这些木棍中切割出来m条长度相同的木棍,问这m根木棍最长有多长?

输入数据
第一行输入两个数字,n(1<=n<=1000)为木棍数目,m(1<=m<=1000)为需要切割出的相同长度的木棍数目 随后n个正整数,表示原始木棍的长度(<=10000)

输出数据
每组输出一行结果,表示切割后绳子的最长长度(保留两位小数)

样例输入

4 5
5 6 7 8

样例输出

4.00

思路
数值的范围问题,可以考虑用二分查找做。
初始区间:[min,max][min,max][min,max] min和max的取值要从题意中找,一个很显然的想法是直接取平均值,但是这并不是最值。min是0.01(再往下,m无限大,无意义;max直接取最极端的情况:lmaxl_{max}lmax​,这个时候m=1)

二分判断:∑inlilcut≥?m\sum_{i}^n{\frac{l_i}{l_{cut}}} \geq_? mi∑n​lcut​li​​≥?​m如果大于,则更新区间为[mid,max][mid,max][mid,max],反之亦然

终止条件:∣max−min∣≤0.01|max-min|\leq 0.01∣max−min∣≤0.01,注意终止条件要写在while内部;而循环条件是max>minmax>minmax>min!!!

特别注意:区间查找的变量类型必须是float/double,上次上课写错了简直社死…

//
// Created by ZixinQin on 2021/9/17.
//
# include <iostream>
# include <cstdio>
# include <algorithm>
# include <cstring>
# include <list>
using namespace std;
int m,n,l[1010];
int length_test(double max,double min){ //千万注意是浮点型double mid = (max+min)/2;int total = 0;for (int i = 0; i < n; ++i) {int num = l[i]/mid;total += num;}if (total >= m) return 1;else return 0;
}int main() {cin>>n>>m;double max = 0;double min = 0.01;for (int i = 0; i < n; i++) {cin>>l[i];if (l[i]> max) max = l[i];//cout<<l[i]<<endl;}//cout<<max<<" "<<min<<endl;while(max > min){if (max - min < 0.005)break;if(length_test(min,max)){min = min + (max-min)/2;}else{max = max - (max-min)/2;}}double mid = (max + min) /2;printf("%.2f",mid);
}

二分查找总结

(摘抄自老师课件)
二分查找(bisection method)是求最值的算法

一般的,公式如下:

李老师的Lucky Number (枚举+优化)

题目
李老师的lucky number 是3,5和7,他爱屋及乌,还把所有质因数只有3,5,7的数字认定为lucky number,比如9, 15, 21, 25等等。请聪明的你帮忙算一算小于等于x的lucky number有多少个?

输入数据
一个正整数x,3=<x<=1000000000000

输出数据
小于等于x的lucky number的个数。

样例输入

49

样例输出

11

思路

暴力法求解

Let x≜3a5b7cx \triangleq 3^a5^b7^cx≜3a5b7c,之后依次枚举a,b,c判断x是否是幸运数。x从3循环到n。注意这里枚举a,b,ca,b,ca,b,c的时候,用到了高中学的换底公式:logxy=log2ylog2xlog_xy=\frac{log_2y}{log_2x}logx​y=log2​xlog2​y​缩小范围。
算法效率:O(n4)\mathcal{O}{(n^4)}O(n4),直接TLE(20).

typedef long long LL;
int lucky_test(int n){for (LL a = 0; a <= log(n)/ log(3); ++a) {for (LL b = 0; b <= log(n)/log(5); ++b) {for (LL c = 0; c <= log(n)/log(7); ++c) {if (pow(3,a)* pow(5,b)* pow(7,c) == n && a+b+c>0){// product should not be 1return 1;}}}}return 0;
}int main() {LL n;cin>>n;int count = 0;for (int i = 3; i <= n ; i++) {if (lucky_test(i))count +=1;}cout<<count;
}

简单优化求解

我们可以发现,在计算过程中是存在一些重复性的。例如,我们判定了x是幸运数后,就可以知道3x,5x,7x也是。那么,我们可以在判定“最后一个幸运数”的时候,把前面的幸运数都顺便找到并记录。

效率:O(n3)\mathcal{O}{(n^3)}O(n3),WA(60).
2021.10.4更新:AC了,错误原因是函数的返回类型和参数类型也要LL!!!!

using namespace std;
typedef long long LL;int lucky_test2(int n){LL a,b,c = 0;LL cnt = 0;for (a = 0; pow(3,a) <= n; ++a) {for (b = 0; pow(3,a)* pow(5,b) <= n; ++b) {for (c = 0; pow(3,a)* pow(5,b)* pow(7,c) <= n; ++c) {if (a+b+c>0){// product should not be 1cnt ++;}}}}return cnt;
}int main() {LL n;cin>>n;LL count = lucky_test2(n);cout<<count;
}

总结

  1. 函数的参数类型一定不要忽略!
  2. for循环的第二个条件、第三个条件是比较灵活的。

李老师的暑假旅行(简单背包:二叉树,DFS)

题目
李老师正准备暑假旅行,他有一个容量为L的行李箱和n个物品(n不超过20),每个物品都有自己的体积,物品可以放入行李箱,但行李箱中物品的总体积不能超过行李箱容量,李老师现在想知道他有多少种携带物品的方案(一个物品都不带也算一种方案)

输入数据
第一行为两个正整数n和L,分别代表物品总数和行李箱容量,n<=20,L<=1e9 接下来一行为n个正整数vi,代表第i个物品的体积,vi<=1e8

输出数据
方案数

样例输入

3 10
2 4 5

样例输出

7

枚举二叉树路径求解

背包问题有很多做法:常见的是DP / 递推;而所有的DP问题,本质上都是有限集中的最值问题。

但是在这一章节中,我们主要讨论的是枚举算法,而枚举的对象,是二叉树的路径(path)。

这里主要是要看懂深度优先搜索(Depth First Search) 这一块。

void dfs(int v[],int id, int vol,int n, int L){

是函数的声明,参数包括:

  • 规划数组 (programming tablet)
  • 状态表示 f(i,j)f (i , j )f(i,j)
  • 其它需要的额外信息(也可以不作为参数,直接写在int main()外面,但是这样不能debug)
    if (id == n){ // 到达了状态尽头if (vol <= L){plan++;} // 判断是否符合条件,是的话就增加全局变量的值return; // 回溯,否则算法无法终止}dfs(v,id+1,vol,n,L); // 没有到达搜索的尽头,因此继续分支(不选第id个)dfs(v,id+1,vol+v[id],n,L); //选了第id个
}

代码实现

//
// Created by ZixinQin on 2021/9/17.
//
using namespace std;int plan = 0;void dfs(int v[],int id, int vol,int n, int L){// terminal stateif (id == n){// check if satisfy requirementif (vol <= L){plan++;}return;}//cout<<id<<" "<<vol<<endl;dfs(v,id+1,vol,n,L);dfs(v,id+1,vol+v[id],n,L);
}int main() {int n,L;cin>>n>>L;int v[21];for (int i=0;i<n;i++)cin>>v[i];dfs(v,0,0,n,L);cout<<plan<<endl;return 0;
}

小结
写DFS:(1)状态表示(不重不漏) (2)状态转移(分到达尽头;没有达到尽头,如果没有达到就分支)

移除石头(NOIP原题)

题目
有一条河,河中间有一些石头,已知石头的数量和相邻两块石头之间的距离。现在可以移除一些石头,问最多移除m块石头后(首尾两块石头不可以移除),相邻两块石头之间的距离的最小值最大是多少。

输入数据
第一行输入两个数字,n(2<=n<=1000)为石头的个数,m(0<=m<=n-2)为可移除的石头数目;随后n-1个数字,表示顺序和相邻两块石头的距离d(d<=1000)

输出数据
输出最小距离的最大值

样例输入

4 1
1 2 3

样例输出

3

思路

二叉树枚举求解

和上一题相似:

  • 枚举每种取or不取的可能,形成一个二叉树。
  • 每个路径对应一种解。 依次计算每个解的最小值,最后取最大值。

代码实现:

//
// Created by ZixinQin on 2021/9/17.
//
# include <iostream>
# include <cstdio>
# include <algorithm>
# include <cstring>
# include <list>
using namespace std;
typedef long long LL;int d_max;int check(int d[],int flag[],int n){int local_min = n*1001;for (int i=1;i<n;i++){if (flag[i]!=1){local_min = min(local_min,d[i-1]); }else{int index = i-1;int acc_d = d[index];while (flag[i]==1){i++;acc_d += d[i-1];}local_min = min(local_min,acc_d);}}return local_min;
}void dfs(int id,int removed,int d[],int flag[],int n,int m){if (id == n){if(removed!=m)return;int dist = check(d,flag,n);if (d_max < dist)d_max = dist;return;}if(removed<m && id!=0 && id!=n-1){flag[id] = 1;dfs(id+1,removed+1,d,flag,n,m);}flag[id] = 0;dfs(id+1,removed,d,flag,n,m);
}int main() {int n,m;int d[1010];int flag[1010];cin>>n>>m;for (int i = 0; i < n-1; ++i) {cin>>d[i];}dfs(0,0,d,flag,n,m);cout<<d_max;return 0;
}

复杂度分析:O(2n)\mathcal{O}(2^n)O(2n),TLE(60)

二分法优化求解

题目的描述是求最小值的最大值。
也就是说,所求问题满足:若c(x)∈Sc(x)\in Sc(x)∈S,则c(p∣p≤x)∈Sc(p|p\leq x )\in Sc(p∣p≤x)∈S的特性。因此,可以构造二分法求解。
在这里,我们给出了两种实现。实现2参考自老师PPT,实现1参考自:https://blog.csdn.net/qq_45249273/article/details/104397953

这道题非常。需要根据数据调整二分法的误差容忍范围(eps),如果选1会WA,选0.01会TLE,选0.1才能全部通过。这非常像机器学习中的调参…

//
// Created by ZixinQin on 2021/9/17.
//
# include <cstdio>
using namespace std;
int n,m;
int a[1002];
int dis[1002];bool validate(int middle)
{int removed=0;int st=1;//最初的石头for(int i=2;i<=n;i++)//i表示结尾的石头{if(dis[i]-dis[st]<middle)//如果相邻距离小于middle,则移动石头removed++;else //否则就更新初始石头st=i;}if(removed>m)return 0;elsereturn 1;
}int validate2(int mid){int k = m; // 可以移动的数目int st=1;for(int end=2;end<=n;){int distance = dis[end]-dis[st];// 类似于双指针算法while (distance < mid){ // 计算次数可以remove多少个石头k--;end++;if (k<0) return 0; //移动的多了,不合理if (end > n){if (st==1) return 0;return 1;}distance = dis[end] -  dis[st]; // accumulated disttance}st = end;end ++;}return 1;
}int main() {dis[1] = 0;scanf("%d %d",&n,&m);for (int i = 2; i <= n; ++i) {scanf("%d",&a[i]);dis[i] = dis[i-1]+a[i];}double lb = 0, ub = 1000*1000+5;while(ub-lb>0.2){double mid = (lb + ub)/2; // distance is of integer typeif (validate2(mid))lb = mid;elseub = mid-1;}printf("%d",(int)lb);return 0;
}

数组元素和(组合优化)

题目描述
给你一个长度为n的数组和一个正整数k,问从数组中任选两个数使其和是k的倍数,有多少种选法
对于数组a1=1 , a2=2 , a3=2而言:
(a1,a2)和(a2,a1)被认为是同一种选法;
(a1,a2)和(a1,a3)被认为是不同的选法。

输入数据
第一行有两个正整数n,k。n<=1000000,k<=1000000 第二行有n个正整数,每个数的大小不超过1e9

输出数据
选出一对数使其和是k的倍数的选法个数

样例输入

5 6
1 2 3 4 5

样例输出

2

样例说明
样例解释:a1+a5=6,a2+a4=6,都是6的倍数
所以符合条件的选法有(1,5),(2,4)

暴力搜索

//
// Created by ZixinQin on 2021/9/17.
//
# include <cstdio>
# include <iostream>
using namespace std;
int a[1000010];int main() {int n,k;cin>>n>>k;for (int i = 0; i < n; ++i) {cin>>a[i];}int acc = 0;for (int i = 0; i < n; ++i) {for (int j = i+1; j < n; ++j) {if ((a[i]+a[j]) % k ==0 )acc++;}}cout<<acc<<endl;return 0;
}

组合优化

//
// Created by ZixinQin on 2021/9/17.
//
# include <cstdio>
using namespace std;
typedef long long LL;
int a[1000010];int main() {int n,k;scanf("%d %d",&n,&k);for (int i = 0; i < n; i++) {int tmp;scanf("%d",&tmp);a[tmp%k] += 1;}LL acc = 0;for (int i = 0; i < k; ++i) {int j = (k-i) % k;if(i>j)break;else if (j==i)acc += 1LL*a[i]*(a[i]-1)/2;elseacc += 1LL*a[i]*a[j];}printf("%lld",acc);return 0;
}

算法作业1:遍历与枚举相关推荐

  1. 算法作业2:分而治之 (持续更新)

    算法系列文章 搜索算法:遍历与枚举 分治算法:修身,齐家,编算法! 大数据实验1: Hadoop, Scala, Spark的安装 分治算法笔记 什么是分治?什么时候用分治? 机器学习中的分治 大数据 ...

  2. CUMTOJ算法作业二

    CUMTOJ算法作业二 问题 A: 单词排序 题目描述 小红学会了很多英文单词,妈妈为了帮小红加强记忆,拿出纸.笔,把 N 个单词写在纸上的一行里,小红看了几秒钟后,将这张纸扣在桌子上.妈妈问小红:& ...

  3. 图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)

    图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS) 阅读本文前,请确保你已经掌握了递归.栈和队列的基本知识,如想掌握搜索的代码实现,请确保你能够用代码实现栈和队列的基本操作. 深度优先遍 ...

  4. 回溯算法 与 深度优先遍历对比

    回溯算法 与 深度优先遍历对比 回溯算法与深度优先遍历 理解 搜索与遍历 与动态规划的区别 共同点 不同点 从全排列问题开始理解回溯算法 设计状态变量 代码实现 参考代码 1 (错误代码): 修改的部 ...

  5. JS-面向对象-操作对象的属性 / 检测对象的某个属性是否存在 / 遍历(枚举)对象的属性 / 属性的分类

    操作对象的属性 <!DOCTYPE html> <html lang="en"> <head><meta charset="UT ...

  6. 数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树

    [本文谢绝转载,原文来自http://990487026.blog.51cto.com] 树 数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树二叉树的创建,关系建立二叉树的创建 ...

  7. 算法作业 (三)——— 装箱问题

    这周的问题终于有些难度了,终于不是一眼看过去就有能写出大概的题目了,还有就是,我又找不到题目了,所以去网上扒了一下,英语的: Packets Time Limit: 1000MS   Memory L ...

  8. 算法作业系列10——Unique Substrings in Wraparound String

    算法作业系列(十) Unique Substrings in Wraparound String 写在前面 如果你是因为没有思路来找答案的,这里建议你去看看题目的s,因为s是一个连续字母的字符串,所以 ...

  9. poi word转html 根号,根号算法 - 作业部落 Cmd Markdown 编辑阅读器

    根号算法 --如何让复杂度去掉维 数据结构 算法 By 分块 一般分块 板子&原理 SIZ=(int)sqrt(n);//块大小 for(inti=(x-1)*SIZ+1;i<=x*SI ...

最新文章

  1. 结对-贪吃蛇游戏-开发环境搭建过程
  2. python3爬取带密码的网站_Python3 爬取网站收藏数超过70的 情侣网名
  3. 基于windows平台的命令行软件安装工具Chocolatey的安装
  4. 51单片机之音乐代码
  5. 重读经典(点云深度学习开山之作):《Deep learning on point clouds for 3D scene understanding》(持续更新中)
  6. 还没使用过Web Worker? 推荐一款开源工具Workerize-Loader,让你在webpack项目中轻松使用Web Worker
  7. 9.22 keep studying
  8. 李迟2021年10月知识总结
  9. Spring Web MVC框架简介
  10. 创建云数据库 Hbase结果表
  11. LaTeX 页眉和页脚
  12. 用户体验五要素--战略层、范围层、结构层、框架层、表现层
  13. Codeforces Round #521 (Div. 3) B. Disturbed People
  14. 2021-09-13 备份
  15. 201819102040张辰飞
  16. 实用的签到、日程表日历控件(可扩展)
  17. 有discuz数据库,忘了管理员密码,怎样进后台
  18. 阿里面试:“说一下从 url 输入到返回请求的过程”
  19. 国外大学网上免费课程
  20. 高质量文章导航-持续更新中

热门文章

  1. HighNewTech:来到了21世纪的第3个十年,各行业数字化迫在眉睫,全民编程也势不可挡。但,问题来了,编程,一定需要写代码么?那么,传说中的iVX工具,与编程到底又有什么暧昧关系?
  2. Odoo产品分析 (三) -- 人力资源板块(6) -- 工资表(1)
  3. nth-child 实用技巧
  4. 头歌C++面向对象 - 类的多态性与虚函数
  5. Python校实训第一天----分支语句之星座查询
  6. 别再说你不会!java嵌入式开发教程
  7. 复盘苏宁收购红孩子案例——思考VC与垂直电商的未来
  8. wordpress主题怎样在标签云效果中显示每个标签的文章数?
  9. keychain介绍
  10. tf 设置多显卡_让显卡再次危机,《孤岛危机》重置版能否找回当年的感动