【问题描述】
市场上有p种桶(每种无限多),容积分别为v[1],v[2],…,v[p]。如果想要买一些桶刚好能量出q升水,请你计算最少需要买几只桶以及买哪几种容积的桶?
如果数量最少的方案有多种,你应选择最小的桶的容积尽量小的方案;如果还有多种方案,那么应选择第二小桶的容积尽量小的方案;……。比如购买容积为 {3,5,7,9} 的四只桶比购买溶积为{3,6,7,8} 要好。
【输入格式】
第1行包含一个整数q,表示要量取水的容积。第2行包含一个整数p,表示市场上桶的种数。接下来p行,每行一个数,依次为每个桶的容积v[1],v[2],…,v[p]。
【输出格式】
输出1行,每两个数间用空格分隔,第1个数k为最少的桶的数量,接下来k个数从小到大输出每个桶的容量。
【输入样例】
16
3
3
5
7
【输出样例】
2 3 5
【数据范围】
q<=20000
p<=100
传送站p2991 岳麓山上打水
【DFS】
【问题分析】
一开始想复杂了,联想到倒水问题,便想着在搜索完要选的容积后在进行一道搜索判断能都用选取的容积量取目标水量。然而想想都觉得复杂,遂弃之。在询问了一dalao后,豁然开朗。
原来此题中,因为选了容积为vi的桶后可以使用无数次,题意即假设有一个大桶M,使用n1个v1的桶,n2个v2的桶……加满水后,全部倒入M中,使得M中的水在某一种情况下==q。(样例即2*3+2*5==8)
题目要求有两个①种类最少;②相同种数下容积最小尽可能小
又因为所选的种数未知,所以理所当然迭代加深~
有一点子集生成的味道,选or不选~
因此可写出伪代码:
bool dfs(int t,int rest,int s)
{
if(s==k)
{
if(rest==0)
{
if(better())更新ans;
return 1
}
return 0;
}
for(int i=0;i*v[t]<=rest;i++)//i*v[t]不能超过剩余水量
{
if(i==0)//不选此种容积
{
操作..... a[z]=v[i];
}
else//选i个此种容积
{
操作....
}
}
}
for(k=1;;k++)
if(dfs(1,q,0))break;
printf("%d",k);
for(int i=0;i<k;i++)printf(" %d",ans[i]);
【细节处理】
bool ok的设定
道理和埃及分数一样,我们并不是找到一个解就行了,而是要搜索出在k种容积的桶下所有的解,并按要求找到最优解。better()函数的书写
由题意“如果数量最少的方案有多种,你应选择最小的桶的容积尽量小的方案;如果还有多种方案,那么应选择第二小桶的容积尽量小的方案”,所以只需从小到大遍历a[]和ans[],一旦发现a[i]<ans[i]就return 1,一旦发现a[i]>ans[i]就return 1。
前提需要搜索前先按升序排序!预处理
要将ans[]全部变为无穷大,便于第一组的录入- 剪枝
因为我们回溯搜索的层数代表的是容积种类,每一层选择有不选(i==0) or 选i个,因此当t>p时就要终止
if(t>n)return false;//考虑数量超过最大值——>避免无意义的搜索
【CODE】
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
#include<queue>
#include<stack>
#define inf 2147483647
using namespace std;
int x,n,v[105],k,cnt=1;
int a[20002],ans[20002];//所取的桶的容积
bool better()
{
for(int i=1;i<=k;i++)
if(a[i]<ans[i])return 1;
else if(a[i]>ans[i])return 0;
return 0;
}
bool dfs(int t,int rest,int s)//考虑第t个是否选,前t-1个剩余rest水,前t-1次已经选了种
{
if(s==k)
{
if(rest==0)//能量取x
{
if(better())
for(int i=1;i<=k;i++)ans[i]=a[i];
return true;
}
return false;
}
if(t>n)return false;//考虑数量超过最大值——>避免无意义的搜索
bool ok=0;
for(int i=0;v[t]*i<=rest;i++)//容积为i的桶所选的数量
{
if(i!=0)//选了该容积
{
int V=v[t]*i;
a[s]=v[t];
//cnt+=1;
if(dfs(t+1,rest-V,s+1))ok=1;
//cnt-=1;
}
else
if(dfs(t+1,rest,s))ok=1;
}
return ok;
}
int main()
{
// freopen("in.txt","r",stdin);
for(int i=1;i<=104;i++)ans[i]=99999;//预处理
scanf("%d%d",&x,&n);
for(int i=1;i<=n;i++)
scanf("%d",&v[i]);
sort(v+1,v+1+n);//升序排列,①便于dfs搜索时先找到小的->从而数量最少
// ②确保搜到的容积也还是升序排列,便于比较
for(k=1;;k++)//取k种容积的桶-->dfs保证桶数最少!!
if(dfs(1,x,0))break;
printf("%d",k);
for(int i=0;i<k;i++)printf(" %d",ans[i]);
return 0;
}
【DP+DFS】
在网上看到大神在判断是否能量取目标水量时用的是DP的思想(完全背包)
But……dp还没学,待日后补足
dp+dfs方法
Written By Mr.Shadow from CQYZ