解题报告:Vijos P1159 岳麓山上打水

前言

  • 题面有删改,若影响理解请留言告知作者。

题目描述

信息组有一个容量为n升的大缸,由于大家都很自觉,不愿意浪费水,所以每次都会刚好把缸盛满。但信息组并没有桶子来舀水,作为组内的生活委员,你必须去买桶子。有m种桶子,每种桶子都有无穷多个,且价钱一样。请你计算购买方案。如果有多种方案,输出字典序最小的那个。

输入输出格式

第1行1个数n(n<=20000)。

第2行1个数m(m<=100)。

接下来m行,每行一个数,依次为每个桶的容积。

输出共1行,每两个数间用空格分隔,第1个数x为最少的桶的数量,接下来x个数从小到大输出每个桶的容量。

输入样例#1

16
3
3
5
7

输出样例#1

2 3 5


代码

#include 
using namespace std;

int n,m,dep;
int vol[105],wgt[105],dp[20005],vis[105];

void init()
{
	scanf("%d%d",&n,&m);
	for (int i = 1;i <= m;i++) scanf("%d",&vol[i]);
	sort(vol + 1,vol + m + 1);
}

bool pack()
{
	for (int i = 1;i <= dep;i++)
		for (int j = wgt[i];j <= n;j++)
			dp[j] = max(dp[j],dp[j - wgt[i]] + 1);
	return (dp[n] > 0);
}

void dfs(int cur)
{
	if (cur == dep + 1)
	{
		memset(dp,-0x3f,sizeof(dp));
		dp[0] = 0;
		if (pack())
		{
			printf("%d ",dep);
			for (int i = 1;i <= dep;i++) printf("%d ",wgt[i]);
			exit(0);
		}
		return;
	}
	for (int i = 1;i <= m;i++)
	{
		if (vis[i]) continue;
		vis[i] = true;
		wgt[cur] = vol[i];
		dfs(cur + 1);
		vis[i] = false;
	}
}

int main()
{
	init();
	for (dep = 1;dep <= m;dep++) dfs(1);
	return 0;
}

分析

  • 算法:迭代加深搜索 + 完全背包。
  • 我们不知道到底要购买哪几种桶子,因此本题使用全排列的方式来枚举所有选取的桶子的种类。附上全排列代码:
#include 
using namespace std;

int n;
int ans[20],vis[20];

void dfs(int dep)
{
	if (dep == n + 1)
	{
		for (int i = 1;i < dep;i++)
			printf("%d ",ans[i]);
		printf("\n");
		return;
	}
	for (int i = 1;i <= n;i++)
	{
		if (vis[i]) continue;
		vis[i] = true;
		ans[dep] = i;
		dfs(dep + 1);
		vis[i] = false;
	}
}

int main()
{
	scanf("%d",&n);
	dfs(1);
	return 0;
}
  • 可是,我们连购买桶子的种数(即全排列中的n)都不知道,若直接DFS就会导致无限递归,因此本题需要枚举这个n,(即dep),用到了迭代加深搜索
  • 题干要求输出字典序最小的,因此要事先将vol数组按从小到大的顺序排序。
  • 枚举出了购买的桶子的种类,本题就转化为完全背包问题了。将桶子的容积看作是物品的体积,桶子的价值为1(毕竟本题不需要求花费),缸的容量n看作背包容量。
  • 如果用当前选出来的这几种桶子可以恰好装满背包(“刚好把缸盛满”),就得到了可行解(而且也是最优解!),此时输出答案。

2019.1.26 upd:本题使用的实际上只是迭代加深搜索,它与真正的IDA*相比少了一个估价函数,特此更正。

作者才疏学浅,若有谬误,敬请斧正。

你可能感兴趣的:(解题报告)