Greedy Tino - 九度教程第 100 题
题目
时间限制:1 秒 内存限制:32 兆 特殊判题:否
题目描述:
Tino wrote a long long story. BUT! in Chinese.So I have to tell you the problem directly and discard his long long story. That is tino want to carry some oranges with “Carrying pole”, and he must make two side of the Carrying pole are the same weight. Each orange have its’ weight. So greedy tino want to know the maximum weight he can carry.
输入:
The first line of input contains a number t, which means there are t cases of the test data.for each test case, the first line contain a number n, indicate the number of oranges.the second line contains n numbers, Wi, indicate the weight of each orange.n
is between 1 and 100, inclusive. Wi is between 0 and 2000, inclusive. the sum of Wi is
equal or less than 2000.
输出:
For each test case, output the maximum weight in one side of Carrying pole. If you can’t carry any orange, output -1. Output format is shown in Sample Output.
样例输入:
1
5
1 2 3 4 5
样例输出:
Case 1: 7
本题大意:有一堆柑橘,重量为 0 到 2000,总重量不大于 2000。要求从中取出两堆放在扁担的两头且两头的重量相等,问符合条件的每堆重量最大为多少。没有符合条件的分堆方式则输出-1。
在求解该问题之前,先关注本题的输入特点。与以往讨论过的问题不同,该例在输入中将预先告诉我们输入的测试数据个数,即整数 T。所以程序只需要准确的处理 T 组数据即可,以免造成不必要的错误。
首先,只考虑柑橘重量为非 0 的情况。因为本题要求解的是重量相等的两堆柑橘中每堆的最大重量,并且在堆放过程中,由于新的柑橘被加到第一堆或者第二堆,两堆之间的重量差会动态发生改变,所以设状态 dp[i][j]
表示前 i 个柑橘被选择后(每个柑橘可能放到第一堆或者第二堆)后,第一堆比第二堆重 j 时(当 j 为负时表示第二堆比第一堆重),两堆的最大总重量和。
初始时,dp[0][0]
为 0,即不往两堆中加任何柑橘时,两堆最大总重量为 0;dp[0][j]
(j 不等于 0)为负无穷,即其它状态都不存在。
根据每一个新加入的柑橘被加入到第一堆或者第二堆或者不加到任何一堆,设当前加入柑橘重量为 list[i],这将造成第一堆与第二堆的重量差增大 list[i]或减小 list[i]或者不变,我们在它们之中取最大值,其状态转移为:
dp[i][j]=max(dp[i-1][j-list[i]]+list[i], dp[i-1][j+list[i]+list[i], dp[i-1][j])
当根据该状态转移方程求出所有的状态后,状态 dp[n][0] / 2
即是所求。
再来考虑柑橘重量包含0的情况,当在不考虑柑橘重量为0,推得dp[n][0]
为正数时,柑橘重量为 0 的柑橘将不对答案造成任何影响,固在这种情况时可直接排除重量为 0 的柑橘。当在不考虑柑橘重量为 0,推得 dp[n][0]
为 0 时,即不存在任何非 0 的组合使两堆重量相等。此时,若存在重量为 0 的柑橘,则可组成两堆重量为 0 的柑橘(至少有一个柑橘重量为 0),它们重量相等;否则,将不存在任何分堆方式,输出-1。
最后分析其复杂度。由于柑橘总重量不大于 2000,所以总的状态数量为柑橘总数 n*2*2000
,状态转移为 O(1)复杂度,所以综合时间复杂度为O(4000*m),在我们可以接收的范围内。
#include <stdio.h>
#define OFFSET 2000
//因为柑橘重量差存在负数的情况,即第一堆比第二堆轻
//所以在计算重量差对应的数组下标时加上该偏移值,
//使每个重量差对应合法的数组下标
int dp[101][4001];//保存状态 (-2000~2000)
int list[101];//保存柑橘数量
#define INF 0x7fffffff//无穷
int main()
{
int T;
int cas=0;//处理的case数,以便输出
scanf("%d",&T);//输入要处理的数据组数
while(T--){//T次循环
int n;
scanf("%d",&n);
bool HaveZero=false;//统计是否存在重量为0的柑橘
int cnt=0;//计数器,记录共有多少个重量非零的柑橘
for(int i=1;i<=n;i++){
//输入n个柑橘重量
scanf("%d",&list[++cnt]);
if(list[cnt]==0){//若当前输入柑橘重量为0
cnt--;//去除这个柑橘
HaveZero=true;//并记录存在重量为0的柑橘
}
}
n=cnt;
for(int i=-2000;i<=2000;i++){
dp[0][i+OFFSET]=-INF;
}//初始化,所有dp[0][i]为负无穷
//注意要对重量差加上OFFSET后读取或调用
dp[0][0+OFFSET]=0;//dp[0][0]为0
for(int i=1;i<=n;i++){//遍历每个柑橘
for(int j=-2000;j<=2000;j++){
//遍历每种可能的重量差
int tmp1=-INF,tmp2=-INF;
//分别记录当前柑橘放在第一堆或第二堆时
//转移得来的新值,若无法转移则为-INF
if(j+list[i]<=2000 && dp[i-1][j+list[i]+OFFSET]!=-INF){
//当状态可以由放在第一堆转移而来时
tmp1=dp[i-1][j+list[i]+OFFSET]+list[i];
//记录该转移值
}
if(j-list[i]>=-2000 && dp[i-1][j-list[i]+OFFSET]!=-INF){
//当状态可以由放在第二堆转移而来时
tmp2=dp[i-1][j-list[i]+OFFSET]+list[i];
//记录该转移值
}
if(tmp1 < tmp2){
tmp1=tmp2;
}//取两者中较大的那个,保存至tmp1
if(tmp1 < dp[i-1][j+OFFSET]){
//将tmp1与当前柑橘不放入任何堆即状态差
//不发生改变的原状态值比较,取较大的值保存至tmp1
tmp1=dp[i-1][j+OFFSET];
}
dp[i][j+OFFSET]=tmp1;
//当前值状态保存为三个转移来源
//转移得到的新值中最大的那个
}
}
printf("Case %d:",++cas);//按题目要求输出
if(dp[n][0+OFFSET]==0){
puts(HaveZero==true ? "0" : "-1");
//根据是否存在重量为0的柑橘输出0或-1
}else{
printf("%d\n",dp[n][0+OFFSET]/2);
//否则输出dp[n][0]/2
}
}
return 0;
}