Code[VS] 1959 拔河比赛

【题意】

一个学校举行拔河比赛,所有的人被分成了两组,每个人必须(且只能够)在其中的一组。要求两个组的人数相差不能超过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;
}


【小结】

①    把解本身当作状态的一个参量,把最优解问题转化为判定性问题

②    在动态规划时,注意控制变量的范围,以降低动态规划的时间复杂度

③    算法的设计要考虑时间复杂度,时间复杂度基于尽可能小、少的变量的循环

你可能感兴趣的:(CodeVS)