枚举算法笔记

枚举算法

不能遗漏、不能重复

设计步骤:

确定枚举对象

 * 参数之间相互独立
 
 * 参数取值范围小

逐一列举可能解

逐一验证可能解

1. 模糊数字问题

//5位数,前3位都模糊不清
int main()
{
  int a1, a2, Obj;
  int num = 0;
  cin>>a1>>a2;
  while(a1!=-1 && a2!=-1)
  {
    int sum = 0;
    for(Obj=100; Obj<=999; Obj++)
    {
      sum = Obj * 100 + a1 * 10 + a2;
      if(sum%57==0 || sum%67==0)
      {
        cout<<sum<<endl;
        num++;
      }
    }
    cout<<"个数: "<<num<<endl;
    cin>>a1>>a2;
  }
  return 0;
}

2. 百钱百鸡问题

钱m,需要购买n只鸡,给出所有的方案。公鸡¥5,母鸡¥3,3只小鸡¥1。

int main()
{
  int m, n; //m钱,n鸡总数
  int ObjCock, ObjChicken, ObjHen;
  cin>>m>>n;
  for(ObjChicken=0; ObjChicken<=n; ObjChicken+=3)
  {
    for(ObjCock=0; ObjCock<=n-ObjChicken; ObjCock++)
    {
      ObjHen = n-ObjChicken-ObjCock;
      if(m == 5 * ObjCock + ObjChicken/3 + 3 * ObjHen)
      {
        cout<<"cock:"<<ObjCock<<" hen:"<<ObjHen
                  <<" chicken:"<<ObjChicken<<endl;
      }
    }
  }
  return 0;
}

3. 数组配对问题

长度位n的数组,任选两个数,使其为正整数k的倍数,问有多少种选法?
(
a1, a2)与(a2, a1)是同一种选法

数学知识:(a1 + a2)%k =0 <==> (a1%k + a2%k)%k =0

先对数组每个元素模k,然后可以分成k组,枚举对象就是每个组。

int main()
{
  long long n, i;
  int k, Num=0; //Num是选法种数
  cin>>n>>k;
  long long arr[n];
  int b[k]={0};
  for(i=0;i<n;i++)
  {
    cin>>arr[i];
    b[(arr[i]%k)]++; //对每个数据模k后,放入容器中
  }

  //简单化,不用分奇偶讨论
  for(i=0; i<=k/2; i++)
  {
       int j = (k-i) % k;  //要考虑余数为0的情况,故需要%k
    if(j == i)
    {
          Num += b[i] * (b[i]-1) / 2;
    }
    else
    {
      Num += b[i] * b[j];
    }
  }
  cout<<"Num of Options: "<<Num<<endl;
  return 0;
}

4. 绳子切割问题

一般化模型:求满足某个条件C(x)的最小的x,C(x)具有单调性。

N条绳子,长度分别为Li(<=1000)若从中切割出k条长度相同的绳子。K条绳子每条最长能有多长?保留2位小数。

输入:N,K,Li

思路:以切出的绳子长度为枚举对象,用二分法枚举

//对绳子长度放大100倍,变为int型变量
int main()
{
  int N, K, i;
  cin>>N>>K;
  double temp;
  int L[N];
  for(i=0; i<N; i++)
  {
    cin>>temp;
    L[i] = 100 * temp;
  }
  int low = 0, high = 100000;
  while(low <= high)
  {
    int middle = (low + high) / 2;
    int sum = 0;
    for(i=0; i<N ; i++)
    {
      sum += L[i] / middle;
    }
    if(sum >= K)
    {
      low = middle + 1;
    }
    else
    {
      high = middle - 1;
    }
  }
  double maximun = high / 100.00; //必须用high,若low>high时,high才是正确答案
  cout<<"max length: "<<maximun<<setprecision(2);
  return 0;
}

5. 移除石头问题

n个石头,相邻两石头的距离位di,可移除m个石头(首尾石头不能移除)。问移除m个石头后,相邻两石头的最小距离的最大值是多少?

2<=n<=1000, 0<=m<=n-2, di<=1000

若用逐一枚举,m即是循环层数,但是m是动态的。

逆向考虑问题:假设结果是d,那么移除m个石头后,任意相邻两个石头的距离都>=d(d是该情况下的最小值)。

此时我们只需对所有可能的d进行枚举。而整个d的序列具有单调性,我们只要满足条件的最大d,所以可以用二分法来减小时间复杂度。(若d满足判定条件C(d),则任意d’<=d,也满足C(d’)。)

#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

static int n, m;  //全局变量
static int a[1002] = {0}; //从1开始,a[i]表示第i块石头距离第i-1块石头的距离
static int dis[1002] = {0};  //d[i]表示第i块石头距离第1块石头的距离

bool check(int d) //检验这个d是否是此情况下的最小值
{
  int k = m;  //还可移动的石头数
  int disTan = 0; //表示目前的石头与前一个石头的距离
  int st = 1, en = st + 1; //st以第一个石头为起点,en表示此次遍历的终点
  while(en <= n)
  {
    disTan = dis[en] - dis[st];
    while(disTan < d)
    {
      k--;
      if(k < 0)
        return false;
      en++;
      if(en > n)
      {
        if(st == 1)
          return false;
        else
          return true;
      }
      disTan = dis[en] - dis[st];
    }
    st = en;  //下一个起点位置,是当前的终点
    en = st + 1;
  }
  return true;
}

int main()
{
  int i;
  cin>>n>>m;
  for(i=2; i<=n; i++) //从第2块石头开始记录
  {
    cin>>a[i];
    dis[i] = dis[i-1] + a[i];
  }
  //二分查找最大的d,以d为枚举对象
  int low = 0, high = dis[n]; //距离最大只能为dis[n]
  while(low <= high)
  {
    int middle = (low + high) / 2;
    if(check(middle))
    {
      low = middle + 1;
    }
    else
    {
      high = middle - 1;
    }
  }
  cout<<high;
  return 0;
}

6. 简单背包问题

有容量为L的行李箱(L<=1e9),和n个物品,(n<=20),每个物品的体积是vi(vi<=1e8)

行李箱中物品的总体积必须小于等于行李箱容积,问有多少种携带物品的方案?不带任何物品也算一种方案。

本质上就是求集合的所有子集(有两种方法):

递归法:实际是遍历二叉树的每一条路径

#include <iostream>
#include <vector>

using namespace std;

static long long L;
static int n, a[20], cont = 0;

void Solution(int (&a)[20], int m, int sum)
{
  if(m == n)  //递归层数限制,m等于n时,其实已经把所有位都遍历了,因为是从第0位开始的
  {
    if(sum <= L)
      cont++;
    return;
  }
  Solution(a, m+1, sum);  //m位取0,sum不加
  Solution(a, m+1, sum+a[m]); //m位取1,sum加
}

//递归法
int main()
{
  cin>>n>>L;
  int i;
  for(i=0; i<n; i++)
  {
    cin>>a[i];
  }
  int sum = 0;
  Solution(a, 0, sum);  //从低位开始
  cout<<cont<<endl;
  return 0;
}

转换成二进制数:遍历n位二进制数

#include <iostream>
#include <vector>

using namespace std;

static long long L;
static int n;
static vector<long long> itemV;
static vector<vector<long long>> allSubV;

void subsets(vector<long long>& V)
{
  int nums = 1<<n;  //子集的总个数,其值是每种情况的十进制数
  for(int i=0; i<nums; i++) //每一个i都是一种子集情况的十进制数,只是2进制,无具体子集内容
  {
    vector<long long> subV; //用subV存放这种子集情况的具体内容
    for(int j=0; j<n; j++)  //遍历集合中每一个元素,判断该元素是否在当前子集情况内
    {
      if(i & (1<<j))  // 1<<j 表示了该元素的二进制存在情况,& 操作可判断该元素是否属于当前子集
      {
        subV.push_back(V[j]);  //放入具体元素
      }
    }
    allSubV.push_back(subV);
  }
}

//用n位的二进制数,表示是否包含该物品
int main()
{
  cin>>n>>L;
  int i;
  for(i=0; i<n; i++)
  {
    int temp;
    cin>>temp;
    itemV.push_back(temp);
  }
  subsets(itemV);   //二进制法,求所有子集

  //遍历该二维vector,并判断是否超重
  int sum, cont = 0;
  vector<vector<long long>>::iterator it = allSubV.begin();
  for(; it!=allSubV.end(); it++)
  {
    sum = 0;
    vector<long long> temp = *it;
    vector<long long>::iterator it1 = temp.begin();
    for(; it1!=temp.end(); it1++)
    {
      sum += *it1;
    }
    if(sum <= L)
      cont++;
  }

  cout<<cont;
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值