题目
给n个有序整数对ai bi,你需要选择一些整数对 使得所有你选定的数的ai+bi的和最大。并且要求你选定的数对的ai之和非负,bi之和非负。
输入
输入的第一行为n,数对的个数
以下n行每行两个整数 ai bi
输出
输出你选定的数对的ai+bi之和
样例输入
5
-403 -625
-847 901
-624 -708
-293 413
886 709
样例输出
1715
数据规模与约定
1<=n<=100
-1000<=ai,bi<=1000
解题思路
刚开始深度优先搜索的方法超时了(69分,错误代码可见文末),根据以往的经验,想到用动态规划来代替。
本题其实是一个变种的背包问题,把每一个数对中的ai视为“物品”,为横坐标;前i个数对中,可以任取k个数对,因此,这k个ai的和即为“背包容量”——纵坐标j,每一个数对中的bi视为“方案数目”,存储在二维数组dp中。又考虑到ai、bi的取值范围是-1000<=ai,bi<=1000,数对的数目取值范围是1<=n<=100,故ai的和的取值范围是[-100000,100000],对应到数组中,映射为[0,200000]。
下面分析状态转移方程:对前m个数对而言,可以任取k个数对,按照如上所述的思路,这k个ai的和加上100000即为纵坐标j,此时dp[m][j]即为与各个ai对应的bi的和;但当不同情况组合的ai之和相等时,为符合题目所要求的“最大值”,我们需要取最大的bi之和;由此类推,可知dp这个动态规划数组中,第i行表示前i个物品中任取k个物品(k<=i),在ai之和为j时,能够获得的bi之和的最大值。因此,得到如下状态转移表达式:
d p [ i ] [ a [ i ] + 100000 ] = b [ i ] ; d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] ) ; d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − a [ i ] ] + b [ i ] ) ; \begin{aligned} &dp[i][a[i]+100000] = b[i];\\ dp[i]&[j] = max(dp[i][j],dp[i-1][j]);\\ dp[i][j] = &max(dp[i][j],dp[i-1][j-a[i]]+b[i]); \end{aligned} dp[i]dp[i][j]=dp[i][a[i]+100000]=b[i];[j]=max(dp[i][j],dp[i−1][j]);max(dp[i][j],dp[i−1][j−a[i]]+b[i]);
上面三个式子总体而言是在列举不同的ai组合情况,并记录下某一ai之和j对应的bi之和的最大值。具体解释如下:第一个式子表示,首先将只选择当前的第i对数列,那么相应的j = a[i]+100000即为b[i];第二个式子表示,如果不考虑使用a[i]和之前的组合搭配构成j,记录下之前所有组合的ai之和j对应的bi之和最大值,值得注意的是,这里包含了只选择ai的情况;第三个表达式表示使用a[i]和之前的第1—(i-1)个物品中任意k个物品组合成的j,若对应的bi之和大于前面列举的情况,则更新值。
最后,计算第n行,j>=100000(原点,j不小于原点标示的值意味着ai之和为非负数)的总和,选取bp[n][j]>=0(意味着bi之和为非负数)且和为最大的输出即可。
(参考博客:http://t.csdn.cn/j6nMq,感谢分享思路!)
易错点
dp二维数组的初始值应当设置为最小int整数,切忌设为0(如果设置为0,那么这个不是由数对产生的0将可能被当作最大值传递下去,从而使得结果很大,且不符合给定的数对能产生的最大值)。
代码
#include<stdio.h>
#define z 200000//原点
const int inf=1<<16;//为了初始化dp数组,设置为最小的负数
int p = z/2;
int dp[101][z+1];//从0到200000
struct article{
int a;
int b;
};
int main()
{
int temp,temp1,temp2,max=0;//max可以设置为0,因为ai,bi之和是非负的
int i,j,n,k=1;//k记录ai,bi不都为负数的数对数目
scanf("%d",&n);
struct article A[n];
for (i=0;i<n;i++)
{
scanf("%d %d",&temp1,&temp2);
if (temp1<0 && temp2<0)//对a、b、sum都没有增加,舍弃
continue;
else
{
A[k].a = temp1;
A[k++].b = temp2;
}
}
k--;//从数目转为下标
for(i=0;i<=k;i++)
for(j=0;j<=z;j++)
dp[i][j]=-inf;
dp[1][p+A[1].a] = A[1].b;//初始化
for (i=2;i<=k;i++)//对每一个“物品”遍历
{
dp[i][p+A[i].a] = A[i].b;
for (j=0;j<=z;j++)
{
dp[i][j] = (dp[i][j]>dp[i-1][j])?dp[i][j]:dp[i-1][j];
if ((j-A[i].a)>=0 && (j-A[i].a)<=z)//防止下标越界
{
temp = dp[i-1][j-A[i].a]+A[i].b;
dp[i][j] = (dp[i][j]>temp)?dp[i][j]:temp;
}
}
}
for (i=p;i<=z;i++)//列数
{
if (dp[k][i]>=0)//bi的和是正数
{
temp = dp[k][i]+i-p;//减掉原点才是真实的ai之和
max = (temp>max)?temp:max;
}
}
printf("%d",max);
return 0;
}
错误代码
直接排除了全为负数的情况后使用深度搜索,时间超限(69分):
#include<stdio.h>
int sum=0;
struct article{
int a;
int b;
int sum;
};
void DFS(int k, int s, struct article A[], int suma, int sumb)
{
int i,t,tempa,tempb;
if (s==k)
{
if (sumb>=0 && suma>=0)
sum = ((suma+sumb)>sum)?(suma+sumb):sum;
return ;
}
for (i=s;i<k;i++)
{
tempa = suma+A[i].a;
tempb = sumb+A[i].b;
DFS(k,i+1,A,suma,sumb);//不选
DFS(k,i+1,A,tempa,tempb);//选择
}
}
int main()
{
int sumb=0,suma=0;
int i,n,t=0,k=0,temp1,temp2;//k记录ai,bi不都为负数的数对数目
scanf("%d",&n);
struct article A[n];
for(i=0;i<n;i++)
{
scanf("%d %d",&temp1,&temp2);
if (temp1<0 && temp2<0)//对a、b、sum都没有增加,舍弃
continue;
else if (temp1>=0 && temp2>=0)//全都为正数的一定选
{
suma+=temp1;
sumb+=temp2;
}
else//一正一负的需要判断是否选
{
A[k].a = temp1;
A[k].b = temp2;
A[k++].sum = temp2+temp1;
}
}
DFS(k,0,A,suma,sumb);
printf("%ld",sum);
return 0;
}