【题意】
一个学校举行拔河比赛,所有的人被分成了两组,每个人必须(且只能够)在其中的一组。要求两个组的人数相差不能超过1,且两个组内的所有人体重加起来尽可能地接近。
【分析】
转一下Code[VS]某位神犇的题解:
这道题目不满足动态规划最优子结构的特性。因为最优子结构要求一个问题的最优解只取决于其子问题的最优解。
就这道题目而言,当前n-1个人的分组方案达到最优时,并不意味着前n个人的分组方案也最优。但题目中标注出每个人的最大体重为450,这就提醒我们可以从这里做文章,否则的话,命题者大可把最大体重标注到长整型。
假设w[i]表示第i个人的体重。c[i,j,k]表示在前i个人中选j个人在一组,他们的重量之和等于k是否可能。显然,c[i,j,k]是boolean型,其值为true代表有可能,false代表没有可能。
那c[i,j,k]与什么有关呢?从前i个人中选出j个人的方案,不外乎两种情况:⑴第i个人没有被选中,此时就和从前面i-1个人中选出j个人的方案没区别,所以c[i,j,k]与c[i-1,j,k]有关。⑵第i个人被选中,则c[i,j,k]与c[i-1,j-1,k-w[i]]有关。综上所述,可以得出:
c[i,j,k]=c[i-1,j,k] or c[i-1,j-1,k-w[i]]。
这道题占用的空间看似达到三维,但因为i只与i-1有关,所以在具体实现的时候,可以把第一维省略掉。另外在操作的时候,要注意控制j与k的范围(0<=j<=i/2,0<=k<=j*450),否则有可能超时。
这种方法的实质是把解本身当作状态的一个参量,把最优解问题转化为判定性问题,用递推的方法求解。这种问题有一个比较明显的特征,就是问题的解被限定在一个较小的范围内,如这题中人的重量不超过450。
【实现】
#include
#include
#include
using namespace std;
int n,w[101],wt;
int f[101][45001],t;
int ps,ps1,down;
int main(void)
{
scanf("%d",&n);
for (inti=1;i<=n;i++) scanf("%d",&w[i]);
for (inti=1;i<=n;i++) wt+=w[i];
f[0][0]=1;
for (inti=1;i<=n;i++)
for (intj=i;j;j--)
for(int k=wt;k>=w[i];k--) f[j][k]|=f[j-1][k-w[i]];
ps=n>>1,ps1=n-ps;
for(down=wt>>1;!f[ps][down]&&!f[ps1][down];down--);
printf("%d%d\n",down,wt-down);
return 0;
}
【小结】
① 把解本身当作状态的一个参量,把最优解问题转化为判定性问题
② 在动态规划时,注意控制变量的范围,以降低动态规划的时间复杂度
③ 算法的设计要考虑时间复杂度,时间复杂度基于尽可能小、少的变量的循环