题目 1910: 蓝桥杯算法提高VIP-求最大值

题目

给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[i1][j]);max(dp[i][j],dp[i1][ja[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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值