题目链接:http://www.rqnoj.cn/Problem_112.html
题目题意很简单,想法也很简单,直接深搜加枚举,不过鉴于数据,得适当的优化下。在求解当一个数被加入的时候,要找出相应的产生的最大面值,开始的时候一直没思路,后来想到可以用动规来枚举,这样这个题目就OK了。用动规的时候求解对于给定的一个面值如何用最少的票凑出来,这样我们就可以很容易的计算出上限。如果直接枚举的话,状态之间就很难判断。
算法:用n元组x[1 ..n] 表示n种不同的邮票面值,约定按照从小到大排列。x[1] = 1 是唯一的选择,此时的最大连续邮资区间是[1..m]。接下来,x[2] 的取值可能范围是x[2..m+1],在一般情况下,已选定x[1,i-1],最大连续邮资区间是[1,r],接下来x[i]的取值可能范围是
[x[i-1]+1 , r+1 ] 。这样可用回溯法来解决连续邮资问题,用树表示解空间。关于上限r的计算可以采用我前面所说的动规方式来解,也可以用全排列生成解。不过需要注意对应的时间代价。
代码如下:
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std ;
const int maxn = 430 ;
int num[10] ;//用于存放中间结果,以及枚举
int dp[maxn][10] ;//用于动规计算一给定邮票面值如何用最少的邮票凑出来
int best[10] ;//用于存放最优结果
void dfs(int , int , int ) ;
int max_dp(int , int) ;
int n ;
int m ;
int r ;
int main()
{
//freopen("data.in" , "r" , stdin) ;
scanf("%d%d" , &n , &m) ;
num[0] = 0 ;
num[1] = 1 ;
int i ;
r = n ;
for(i = 2 ; i <= n + 1 ; i ++)
{
dfs(i , 2 , n) ;
}
for(i = 1 ; i <= m ; i ++)
{
printf("%d " , best[i]) ;
}
printf("\n") ;
printf("MAX=%d\n" , r) ;
return 0 ;
}
void dfs(int x , int layer , int to)
{
int i ;
int temp ;
if(layer > m)
{
if(to >= r)//此处需要注意这个条件,在这里wrong了一次
{
r = to ;
for(i = 1 ; i <= m ; i ++)
{
best[i] = num[i] ;
}
}
return ;
}
num[layer] = x ;
temp = max_dp(layer , to) ;
for(i = num[layer] + 1 ; i <= temp + 1 ; i ++)
{
dfs(i , layer + 1 , temp ) ;
}
}
int max_dp(int layer , int to)
{
int i ;
int j ;
memset(dp , 0 , sizeof(dp)) ;
i = 0 ;
//利用动规求解上限r
while( dp[i][layer] <= n )
{
++i ;
for(j = 1 ; j <= layer ; j ++)
{
if(i >= num[j] && j > 1)
dp[i][j] = dp[i][j-1] < (dp[i-num[j]][j] + 1) ? dp[i][j-1] : (dp[i-num[j]][j] + 1) ;
else if(j > 1)
dp[i][j] = dp[i][j-1] ;
else if(i >= num[j])
dp[i][j] = dp[i-num[j]][j] + 1 ;
}
}
return --i ;
}