SSL 1638 拔河比赛

这篇博客介绍了如何使用动态规划算法解决拔河比赛中分组的问题,确保两组人数相差不超过1且体重总和尽可能接近。通过设置状态转移方程,博主详细解析了代码实现过程,最终找到最优解。动态规划在这里起到了关键作用,优化了分组策略。

题目描述:

一个学校举行拔河比赛,所有人被分成了两组,每个人必须且只能够在其中一组,要求两个组的人数相差不能超过1,且两个组内所有人的体重加起来进可能地接近。

样例输入:

3
100
90
200

样例输出:

190 200


解题思路:

由于单个人最大重量比超过450,是一个极小的值,因此可以考虑将解(即重量)本身作为状态中的一个参量,将问题转换为判定性问题,通过判定解是否存在来找到最优解。

fi,j,kf_{i,j,k}fi,j,k 表示从 iii 个人中选 jjj 个人,组成总重量为 kkk 的组合是否可能存在。
由此可推知, fi,j,kf_{i,j,k}fi,j,k 的状态无非就是从这两种状态转移而来的:
若不选第 iii 个人,那么从 iii 个人中选 jjj 个人无非就转移成了 从 i−1i-1i1 个人中选 jjj 个人,也就是 fi−1,j,kf_{i-1,j,k}fi1,j,k
若选第 iii 个人,那么 从 iii 个人中选 jjj 个人无非就转移成了从 i−1i-1i1 个人中选 j−1j-1j1 个人,组成重量为 k−wik-w_ikwi 的组合,即 fi−1,j−1,k−wif_{i-1,j-1,k-w_i}fi1,j1,kwi
那么即可推出动态转移方程:
fi,j,k=fi−1,j,k   or   fi−1,j−1,k−wif_{i,j,k}=f_{i-1,j,k}\ \ \ or\ \ \ f_{i-1,j-1,k-w_i}fi,j,k=fi1,j,k   or   fi1,j1,kwi

这样子一来,最优解就是: 当 fn,n/2,i=truef_{n,n/2,i}=truefn,n/2,i=truefn,n/2+n mod 2,i=turef_{n,n/2+n~mod~2,i}=turefn,n/2+n mod 2,i=ture 时的最小 iii


CODE:

#include <iostream>
#include <cmath>
using namespace std;
int n,w[110],sum=0,ans1,ans2=0;
bool f[110][45001]={false};  //由于f[i]的转移只与f[i-1]有关,因此可以省去一维
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>w[i],sum+=w[i];
	f[0][0]=true;  //初始条件
	for(int i=1;i<=n;i++)  //枚举总共人数
	  {
	  	for(int j=1;j<=i/2+1;j++)  //枚举每一组的人数
	  	  {
	  	  	for(int k=j*450;k>=max(j,w[i]);k--)  //枚举每一组人数可能的重量
	  	  	  {
	  	  	  	f[j][k]=f[j][k]||f[j-1][k-w[i]];  //状态转移
			  }
		  }
	  }
	ans1=(1e9);
	for(int i=n/2;i<=n/2*450;i++)  //找到最优解
	  if(f[n/2][i]&&f[n/2+n%2][sum-i])
	    {
	    	if(abs(ans1-ans2)>abs(i-sum+i))
	    	  ans1=i,ans2=sum-i;
		}
	cout<<min(ans1,ans2)<<" "<<max(ans1,ans2);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值