分糖果(动态规划)

问题描述:假定给 n 个人分糖,n 个人所拥有糖数量的初始值为 A[0], A[1], ... A[n-1]。现在要给这 n 个人分糖,每次分糖都要遵循下述三条规则之一:

(1) 除了选定的一个人外,给所有其他人 1 颗糖;

(2) 除了选定的一个人外,给所有其他人 2 颗糖;

(3) 除了选定的一个人外,给所有其他人 5 颗糖。

问: 至少要分多少次糖才能使这 n 个人的糖果数相同


分析:用Min记录A数组的最小值,不妨设有c[i]次选定第 i 个人,d[i]表示在这c[i]选定中第 i 个人没有分得的糖果总数,则对任意的i , j,都有d[i] - d[j] = A[i] - A[j],即第i个人与第j个人没有分到的糖果数之差与他们的初值之差相同。比如

A: 1 ,5 ,5

方式一: 

             第1次选定A[1],给A[0],A[1]各分5颗糖,现在A: 6, 5, 10;第2次选定A[0],给A[1],A[2]分1颗糖,现在A: 6, 6, 11;第3次选定A[2],给A[0], A[1]分5颗糖,最后都为11颗糖。完成分配,分配次数为3。现在来看下,A[0],A[1],A[2]各有1次被选定,即c[0]=c[1]=c[2]=1;d[0]=1,d[1]=5,d[2]=5,他的的差值与初始值差值一样


方式二:

            第1、2次都选定A[1],每次给A[0],A[2]各分2颗糖,分完后A分别为 3, 5, 7;  5, 5, 9 。

            第 3、4次选定A[2],每次给A[0],A[1]各分2颗糖,分完后A分别为7, 7, 9;;    9, 9, 9 。

            共分4次,c[0]=0, c[1]=2, c[2]=2; d[0]=0,d[1]=4,d[2]=4。


        现在假设在 k 次分配后完成工作,则有k = step(d[0]) + step(d[1]) + ... +step(d[n-1]); 其中step(n)表示分n颗糖果需要的最少次数,step(n) = n/5 + n%5/2 + n%5%2;

注意可能会有 step(n) >= step(n+1),step(n+2),step(n+3),step(n+4), 比如step(9) = 4 > step(10)=2,但是一定会有step(n)<step(n+5);  而我们要解决的是怎么选择d[0], d[1], ... , d[n-1]使得 k 最小,不妨设Min = A[m],则 k 只与d[m]有关,因为d[i](i!=m) 与d[m]的差等于A[i]-A[m];d[m]=0(即没有一次选定A[m])是不是一定能保证 k 最小呢?

不一定。正如刚才的分析,d[m]=0能保证step(d[m])为0是最小的,但不能保证step(d[i])最小,就拿A: 1, 5 , 5举例,方式二d[0]=0,方式一d[0]=1,而且方式一最后的糖果数为11,方式二最后的糖数为9,看起来方式二更优,实际上相反。原因是什么? 因为在方式一中虽然d[0] =1 ,d[1]=5, d[2]=5的每一个都要比方式二的d[0]=0, d[1]=4, d[2]=4都要大,但是,step(4)=2, step(5)=1,问题就在这。 所以我们在找最小k时,要针对d[m] = 0,1,2,3,4求出来的 k 取最小值,这样得到的结果才是真正的最少。

程序中dp[i]就是step(i)

#include<fstream>
#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;
#define _MAX 1000000007


int minStep(int A[],int n)
{
	int i=0,Min = _MAX,Max = -1;
	while(i<n){ Min = min(Min,A[i]); Max = max(Max,A[i]); i++;}
	int sz=Max-Min+10, *dp = (int*)malloc(sz*sizeof(int));
	dp[0]=0;
	for(i=1;i<sz;i++){
		dp[i] = dp[i-1]+1;
		if(i>=2) dp[i] = min(dp[i],dp[i-2]+1);
		if(i>=5) dp[i] = min(dp[i],dp[i-5]+1);
	}
	int res = _MAX;
	for(i=Min;Min-i<5;i--){
		int sum=0;
		for(int j=0;j<n;j++) sum+=dp[A[j]-i];
		res=min(res,sum);
	}
	free(dp);
	return res;
}
int main() {
	// Enter your code here. Read input from STDIN. Print output to STDOUT  
	freopen("input.txt","r",stdin); 
	int T; scanf("%d",&T);
	for(int i=0;i<T;i++){
		int n;
		scanf("%d",&n);
		if(n==0){ printf("0\n"); continue;}
		int *A = new int[n];
		for(int j=0;j<n;j++) scanf("%d",&A[j]); 
		
		printf("%d\n",minStep(A,n));
		delete A;
	}
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值