1.枚举
穷举所有可能的解: 算法枚举通过尝试所有可能的组合或排列来解决问题,确保不会错过任何潜在的解。并进行验证和比较,找到最优解。或者所有解。
解空间的类型:
可以是一个范围的所有数字(或二元组,字符串),或者满足某个条件的所有数字。
蓝桥杯一题
枚举问题
小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0),在 1 到 40 中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574。
请问,在 1 到 n 中,所有这样的数的和是多少?
输入描述
输入格式:
输入一行包含两个整数 (1≤n≤1e4)。
输出描述
输出一行,包含一个整数,表示满足条件的数的和
#include <bits/stdc++.h>
using namespace std;
bool f(int x)
{
while(x)
{
int y=x%10;
if(y==2||y==0||y==1||y==9) return true;
x=x/10;
}
return false;
}
int main()
{
int n;cin>>n;
int ans=0;
for(int i=1;i<=n;i++)
{
if(f(i)) ans+=i;
}
cout<<ans<<'\n';
return 0;
}
代码解析:
bool f(int x)
: 定义了一个函数 f
,该函数返回一个布尔值,表示数字 x
是否包含数字2、0、1、9。函数内部通过对 x
进行取余和整除操作,逐位检查是否包含目标数字。
将 return false;
放在 while
循环外面的原因是确保在检查完所有位数后才返回 false
。这是因为在该问题中,我们希望检查整个数字,而不仅仅是个位数。return
语句的作用是终止函数的执行,并将指定的值(在这里是 true
)返回给调用者。
假设我们有一个数字 x = 23
。如果将 return false;
放在 while
循环内部,那么在检查到数字2后,循环就会终止,直接返回 false
。但实际上,这个数字 23
是符合条件的,因为它包含数字2。
通过将 return false;
放在 while
循环外面,我们确保循环会继续检查更高位的数字,直到整个数字都被检查完。只有在整个数字中没有包含2、0、1、9时,才返回 false
。这样可以保证我们得到正确的结果,即判断整个数字是否符合条件。
遍历输入的数字的每一位: 使用 while (x)
循环,不断执行 x = x / 10;
,这样就可以逐位访问输入数字 x
的各个位数。在每次循环中,通过 x % 10
取余操作获取当前位的数字。
检查每一位是否是目标数字: 使用 if (y == 2 || y == 0 || y == 1 || y == 9)
,判断当前位的数字 y
是否是目标数字之一(2、0、1、9)。如果是,说明当前数字符合条件。
整个数字是否符合条件: 如果在遍历的过程中发现任意一位数字是目标数字之一,就返回 true
,表示整个数字符合条件。如果遍历完整个数字都没有找到目标数字,就返回 false
,表示整个数字不符合条件。
这种遍历每一位数字的方式允许我们检查整个数字的每个部分,以确定是否满足特定的条件。在这个具体的例子中,条件是数字中是否包含2、0、1、9中的任何一个数字。
第二题
给定三个整数 a,b,c,如果一个整数既不是 a 的整数倍也不是 b 的整数倍还不是 c 的整数倍,则这个数称为反倍数。
请问在 1 至 n 中有多少个反倍数。
输入描述
输入的第一行包含一个整数 n。
第二行包含三个整数 a,b,c,相邻两个数之间用一个空格分隔。
其中,1≤n≤1000000,1≤a≤n,1≤b≤n,1≤c≤n。
输出描述
输出一行包含一个整数,表示答案。
#include <bits/stdc++.h>
using namespace std;
int a, b, c;
bool f(int x)
{
if (x % a != 0 && x % b != 0 && x % c != 0)
return true;
return false;
}
int main()
{
int n;
cin >> n;
cin >> a >> b >> c; // 读取输入并赋值
int ans = 0;
for (int i = 1; i <= n; i++)
{
if (f(i))
ans++;
}
cout << ans << '\n';
return 0;
}
代码解析:
如果上述条件为真,即 x
不是 a
, b
, c
的任何一个数的倍数,return true;
将立即结 束函数的执行,并返回 true
,表示 x
是反倍数。
如果上述条件为假,即 x
是至少一个数的倍数,那么 return false;
将结束函数的执行,并返回 false
,表示 x
不是反倍数。
这段代码的作用是在检查一个整数是否同时不是 a
, b
, c
任何一个数的倍数,如果是,返回 true
,否则返回 false
。
n*m 的矩阵中,有一个数字出现了超过一半的次数,请设计一个高效算法找到这个数字。
输入格式
输入第一行包含两个整数 n 和 m,表示矩阵的大小 (1≤n,m≤1000)
接下来 n 行,每行包含 m 个正整数,表示矩阵中的元素。
输出格式
输出一个整数,表示矩阵中出现次数超过一半的数字
#include <bits/stdc++.h>
using namespace std;
map<int,int> mp;
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int n,m; cin>>n>>m;
for(int i=1;i<=n*m;++i)
{
int x;cin>>x;
mp[x]++;
}
for(const auto &[x,y]:mp)
{
if(2*y>n*m/2)
cout<<x<<'\n';
}
return 0;
}
代码分析:
map
是一种关联容器,它提供了一种通过键(key)来快速检索值(value)的机制。每个键都是唯一的,而且它们按一定的顺序存储,通常是从小到大的顺序。
const auto &[x, y]
使用了结构化绑定,将每个 map
中的键值对解构为 x
和 y
。x
表示数字,y
表示该数字在矩阵中出现的次数。
如果某个数字在整个矩阵中出现了超过一半的次数,那么 2 * y
将超过 n * m / 2
。
这样的判断条件允许你筛选出在输入矩阵中出现频率较高的数字,而这些数字的出现次数超过了矩阵总元素个数的一半。
假设你有一个输入序列是 [3, 1, 2, 3, 2, 1, 3]
。在这个序列中,有数字 1、2 和 3。
现在,使用 map
来记录每个数字在序列中出现的次数:
- 初始时,
map
是空的。 - 遍历序列,对每个数字执行
mp[x]++
操作。
第一次遇到数字 3:
mp[3]
不存在,所以它会被创建,值为1。此时,map
中存储了{3: 1}
。
第二次遇到数字 1:
mp[1]
不存在,所以它会被创建,值为1。此时,map
中存储了{1: 1, 3: 1}
。
第三次遇到数字 2:
mp[2]
不存在,所以它会被创建,值为1。此时,map
中存储了{1: 1, 2: 1, 3: 1}
。
第四次遇到数字 3:
mp[3]
存在,所以它的值会加1。现在,map
中存储了{1: 1, 2: 1, 3: 2}
。
以此类推,最终 map
中的内容就是每个数字及其在输入序列中的出现次数。
对于代码中的 mp[x]++;
来说,它的作用就是维护一个计数,记录每个数字在输入序列中出现的次数。最终,通过遍历 map
,你可以找到在序列中出现次数超过一半的数字。
for(int i=1; i<=n*m; ++i)
这个循环用于遍历矩阵中的每一个元素。在这个上下文中,矩阵的行数是 n
,列数是 m
,所以 n * m
表示矩阵中元素的总个数。
i
的范围是从1到n * m
。- 在每次循环迭代中,
i
的值代表了矩阵中的一个位置。
考虑一个简单的例子,假设 n = 3
且 m = 4
,那么 n * m
就是 12。因此,这个循环将会执行12次,分别代表矩阵中的12个位置。
这样的循环适用于一维数组表示的矩阵,通过计算行和列的方式来模拟矩阵的二维结构。在每次迭代中,你可以通过 i
的值计算出当前位置在矩阵中的行和列。
2.贪心算法
贪心算法(Greedy Algorithm)是一种解决问题的算法范式,通常用于优化问题。在贪心算法中,每一步都采取局部最优的选择,希望通过局部最优解最终达到全局最优解。该算法不一定总是得到全局最优解,但在某些情况下,贪心算法的局部最优解也能够满足问题的要求。
贪心往往和排序,优先队列一起出现
蓝桥杯例题
小蓝是机甲战队的队长,他手下共有 n 名队员,每名队员都有一个战斗力值 ��wi。现在他需要将这 n 名队友分成两组 a 和 b,分组必须满足以下条件:
- 每个队友都属于 a 组或 b 组。
- a 组和 b 组都不为空。
- 战斗力差距最小。
战斗力差距的计算公式为 ∣max(a)−min(b)∣,其中 max(a) 表示 a 组中战斗力最大的,min(b) 表示 b 组中战斗力最小的。
请你计算出可以得到的最小战斗力差距。
输入格式
第一行一个整数 n,表示队员个数。
第二行 n 个整数 1,2,3....w1,w2,w3....wn,分别表示每名队友的战斗力值。
数据范围保证:2≤n≤100000,1≤≤wi≤1000000000。
输出格式
输出一个整数,表示可以得到的最小战斗力差距。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int a[N];
int main() {
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
int ans=a[1]-a[0];
for(int i=0;i<n-1;i++)
ans= min(ans,a[i+1]-a[i]);
cout<<ans<<'\n';
return 0;
}
代码解析:
i
的范围是从 1 到 n
,那么在最后一次循环中,a[i+1]
就会越界,因为在数组 a
中,a[n+1]
不存在。所以为了避免数组越界错误,循环的范围是 1
到 n - 1
,确保在每次循环中都可以安全地访问 a[i]
和 a[i+1]
。
这种方式在处理相邻元素的问题时是常见的,以确保不会访问数组越界。
第二题
在很久很久以前,有 n 个部落居住在平原上,依次编号为 1 到 n。第 i 个部落的人数为 ti。
有一年发生了灾荒。年轻的政治家小蓝想要说服所有部落一同应对灾荒,他能通过谈判来说服部落进行联合。
每次谈判,小蓝只能邀请两个部落参加,花费的金币数量为两个部落的人数之和,谈判的效果是两个部落联合成一个部落(人数为原来两个部落的人数之和)。
输入描述
输入的第一行包含一个整数 n,表示部落的数量。
第二行包含 n 个正整数,依次表示每个部落的人数。
其中,1≤n≤1000,1≤n≤10000,1≤ti≤10000。
输出描述
输出一个整数,表示最小花费。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
priority_queue<int, vector<int>, greater<int>> pq; // 最小堆
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
pq.push(x);
}
int ans = 0;
for (int i = 0; i < n - 1; i++)
{
int x = pq.top();
pq.pop();
int y = pq.top();
pq.pop();
ans += x + y;
pq.push(x + y);
}
cout << ans << '\n';
return 0;
}