2016.5.26

1.拔河问题

又是0/1背包问题的变式 ~

人懒直接转载了...别人说的也确实很好 ~

注:以下转载,有自己的稍微改动,侵删。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

问题描述:n 个人参加拔河比赛,每个人有自己的重量,现在需要把他们分成两组进行比赛,每个人属于其中的一个组。为使比赛公平,求使得两组重量差最小的分配。

分析问题:明显的,我前面已经说明,是要采用0-1背包来求解。

问题中要求:最终的队员分配要使得两组队的重量差最小,那么如何才可以实现呢?我们可以这样考虑:

假设所有队员的总质量为sum,那么两队A,B的平均质量 average = sum/2.

那么假如队员分配之后,A队的总重量“接近” average,那么间接的,B队的总重量也会”接近“average。这里的“接近”指的是队中的总重量 大于 或者 小于(当然也有可能是相等)average某一个值,这个值相对来说是小的。那么这样分配的最终结果就会呈现出:两组队的重量差最小。

那么现在我们就可以采用 0-1背包来解决这个问题。

也就是说,这个背包的大小是 average,那么在可供选择的队员中选择,使到,选择的队员总质量不超过 <= average (也就是“接近”average),那么这样就可以使选择的队员总质量达到最接近average,那么间接的,剩下的所有队员都给另一个,其总质量就不小于 >= average。这样两队之间的质量差也就可以最小了。


问题升级:关于拔河比赛,还有一种问题描述:

n 个人参加拔河比赛,每个人有自己的重量,现在需要把他们分成两组进行比赛,每个人属于其中的一个组,两组的人员个数相差不能超过1。为使比赛公平,求使得两组重量差最小的分配。

其实就是多了一个限制条件,两组队员之间的队员人数不能超过1。看到上面的运行例子,可以发现,两队之间的队员分别是 A:2 ;B : 4。所以明显的,不符合题目的要求,所以代码要改进。

那么怎么考虑呢?

首先,我们要肯定的是,上面使用到的 0-1背包策略都是没有问题了,这个问题的求解也是这个过程。而且,这样求解出来的质量差是最小的。如果添加了这个限制条件,那么质量差不一定是最小的了,这个应该是可以理解到的。那么对于这个限制条件怎么实现呢?

我们可以这样想:如果经过上面的求解过程出来的分配结果刚好 A队和B队之间的队员数量相查不超过1,那么最好了,不用额外的处理。那么假如恰好二者相差超过1,例如上面的例子:A队有2人;B队有4人。那么怎么样才可以实现两队之间队员相差1呢?明显要从B队中选一人给A队,这样两队都是3人。那么在B队中选择哪一名队员给A呢?当然要选择B队中重量最小的,这样才可以保证两组重量差最小。(这里可以保证该解是正确的,可以用反证法)

好了,解决的策略出来了,就是要在上面代码的基础上添加一个人数的判断,如果人数相差超过1,就从多人的一队中选择一名重量最小的给队员数少的一队,直到两队的人数相差不超过1.

考虑到是要从一个队中选择一个重量最小的出来,前面的代码使用的是stack来装载队员,明显这里不是很适合,那么我们可以选用优先队列来装载队员,那么在选重量最小的队员出来的时候,优先队列就很方便了。 

#include <iostream>  
#include <queue>  
#include <cmath>  
using namespace std;  
  
//定义一个拔河队员的结构体  
typedef struct people  
{  
    int number; //队员编号,从1开始  
    int weight; //队员重量  
    char team;  
    //为了使得两组的人员个数相差不能超过1,所以增加一个变量用于确定这名队员是A还是B。  
    //确定的规则是,如果一方较多,那么找出一个质量最小的给另一个对,再看看两个队直接的人数是否相差1;  
    //如果不是,那么继续选一个质量最小的给另一队。  
  
    friend bool operator < (people p1,people p2)  
    {  
        return p1.weight > p2.weight; //重量小的优先  
    }  
  
} peoples;  
  
const int N = 100;  
const int W = 5000;  
  
int arr[N+1][W+1];  
//二维表用于记录,其中行下标表示人的编号,列下标表示背包质量,也就是每一队的质量,假定不会超过5000.  
  
int getMax(int a,int b)  
{  
    return a>=b?a:b;  
}  
  
int main()  
{  
    cout<<"输入人数:";  
    int num;  
    cin>>num;  
  
    peoples *players = new peoples [num+1]; //创建num名队员,队员编号从1开始  
  
    int sum = 0; //计算所有人的总质量  
    cout<<"输入每一个人的质量:";  
    //输入每一个人的质量  
    for(int i=1; i<=num; i++)  
    {  
        int w = 0;  
        cin>>w;  
        players[i].number = i;//队员编号  
        players[i].weight = w; //队员的重量  
        players[i].team = 'B'; //假设都是B队的队员  
        sum+=w;  
    }  
    int average = sum/2;  
  
    //初始化二维表中的第一行  
    for(int k=0; k<=W; k++) arr[0][k] = 0;  
    //初始化二维表中的第一列  
    for(int k=0; k<=N; k++) arr[k][0] = 0;  
  
    //逐行进行填表  
    for(int i=1; i<=num; i++) //人的编号从 1~N  
    {  
        for(int j=1; j<=average; j++) //每一队质量从 1~average  
        {  
            arr[i][j] = arr[i-1][j];  
            if(players[i].weight <= j)  
            {  
                arr[i][j] = getMax(arr[i][j],arr[i-1][j-players[i].weight]+players[i].weight);  
            }  
        }  
    }  
  
    //输出当前用到的二维表  
//    for(int i=0; i<=num; i++) //物品编号从 1~N  
//    {  
//        for(int j=0; j<=average; j++) //背包容量从 1~W  
//        {  
//            cout<<arr[i][j]<<"  ";  
//        }  
//        cout<<endl;  
//    }  
  
    int remainspace = average;  
    //选出A队的队员  
    for(int i=num; i>=1; i--)  
    {  
        if (remainspace >= players[i].weight)  
        {  
            if ((arr[i][remainspace]-arr[i-1][remainspace-players[i].weight])==players[i].weight)  
            {  
                players[i].team = 'A'; //选出是A队中的队员  
                remainspace = remainspace - players[i].weight;  
            }  
        }  
    }  
  
    //第一轮分配队员的结果  
    for(int i = 1;i<=num;i++)  
    {  
        cout<<"Team:"<<players[i].team<<"   "<<"Number:"<<players[i].number<<"   Weight:"<<players[i].weight<<endl;  
    }  
  
    priority_queue<peoples> playerA; //创建队伍A  
    priority_queue<peoples> playerB; //创建队伍B  
  
    //统计A队和B队的队员  
    for(int i = 1; i<= num; i++)  
    {  
        if(players[i].team == 'A')  
        {  
            playerA.push(players[i]);  
        }  
        else  
        {  
            playerB.push(players[i]);  
        }  
    }  
  
    int countA = playerA.size();  
    int countB = playerB.size();  
  
    //为了确保A B两队人数相差不超过1,进行调整  
    //如果A B队的队员人数相差不是1的处理  
    while(abs(countA-countB)>1)  
    {  
        //如果A队的队员比较多,那么就要从A队中选出一个重量最小的给B队  
        if(countA>countB)  
        {  
            playerB.push(playerA.top());  
            playerA.pop();  
            countA--;  
            countB++;  
        }  
        //如果B队的队员比较多,那么就要从B队中选出一个重量最小的给A队  
        else if(countA<countB)  
        {  
            playerA.push(playerB.top());  
            playerB.pop();  
            countA++;  
            countB--;  
        }  
    }  
  
    cout<<"A:"<<endl;  
    int sumA = 0;  
    while(!playerA.empty())  
    {  
        cout<<"Number:"<<playerA.top().number<<"   Weight:"<<playerA.top().weight<<endl;  
        sumA+=playerA.top().weight;  
        playerA.pop();  
    }  
    cout<<"A team sum of weight = "<<sumA<<endl;  
  
    cout<<endl;  
  
    //输出B队成员  
    cout<<"B:"<<endl;  
    int sumB = 0;  
    while(!playerB.empty())  
    {  
        cout<<"Number:"<<playerB.top().number<<"   Weight:"<<playerB.top().weight<<endl;  
        sumB+=playerB.top().weight;  
        playerB.pop();  
    }  
    cout<<"B team sum of weight = "<<sumB<<endl;  
  
    cout<<endl<<"两队之间的重量差:"<<abs(sumA-sumB)<<endl;  
  
  
    delete [] players;  
    return 0;  
}  



-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值