枚举算法
不能遗漏、不能重复
设计步骤:
①确定枚举对象
* 参数之间相互独立
* 参数取值范围小
②逐一列举可能解
③逐一验证可能解
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;
}