基础算法枚举,贪心

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 中的键值对解构为 xyx 表示数字,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 = 3m = 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] 不存在。所以为了避免数组越界错误,循环的范围是 1n - 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;
}

  • 44
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值