HDU5697 2016百度之星初赛Astar Round2B 刷题计划

这是一道求最小的sum( x ) * sum( y )的题目

可以切掉BZOJ2395做为这道题的预备知识

将BZOJ2395的Kruskal换成DP大概就是这道题的解法

问题描述:大赛将至,摆在你面前的是 n 道题目,第  i(1in)  道题目能提升  ai  点智力值,代码量为  bi  KB,无聊值为  ci  ,求至少提升 m 点智力值的情况下,所做题目代码量之和* *
无聊值之和最小为多少。

题解:

在平面直角坐标系上,x轴表示sum(x),y轴表示sum(y),那么每一组sum(x) , sum(y)就可以表示成在平面直角坐标系上面的点了。

别人博客里偷来的图,凑合着看吧。

我们要求x*y最小的点,可以理解为过该点的反比例函数(第一象限)最靠近坐标轴。

这个结论是这一类题目算法的基础,算法的内容很简单,递归寻找比(x1 , y1)  (x2 , y2)更优的点。算法大致分为三步:

1、寻找比点A(x1 , y1)  B(x2 , y2)更优的答案,即为距离过这两点直线最远的点(靠近坐标轴一侧的点)。距离这两点直线最远的点C,就是使三角形ABC面积最大的那个点(小学数学知识微笑)。这个面积可以用向量AC向量BC叉积的一半表示。根据这个更新1个价值(更新方法附在后面);

2、以步骤1得出来的1个价值解决问题。这道题里面解决问题的方法是背包动态规划,每个物品的价值由两个价值变成1个价值(理解这一步很重要)。

3、递归寻找AC,CB;

价值更新方法:

设坐标:A( A.x , A.y ) , B( B.x , B.y ) , C ( x0 , y0 );

向量BC × 向量AC 

    = (B.x - x0 , B.y - y0) × (A.x - x0 , A.y - y0)

    = B.x*A.y - A.y*x0 - B.x*y0 + x0*y0 - B.y*A.x + A.x*y0 + B.y*x0 -x0*y0 (展开)

    = (B.x*A.y - B.y * A.x) + ( -A.y + B.y ) * x0 + ( -B.x + A.x) * y0 (将与x0,y0无关的合并)

x0,y0的系数为 ( -A.y + B.y )和 ( -B.x + A.x) ,对于之前价值1× ( -A.y + B.y ) 加 价值2 ×  ( -B.x + A.x) 即为新价值。

用这个DP就可以了;

细节问题:

1、注意long long

2、在整个DP过程中所用的关键词只有一个新价值,而不是用问题原本的价值。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 500 
#define M 850

using namespace std;

struct point{int x,y;point(const int &A,const int &B){x=A;y=B;}point(){}};
typedef point Vector;
Vector operator - (const point &a,const point &b){return Vector(a.x-b.x,a.y-b.y);}
inline long long Cross(Vector A,Vector B){return 1LL*A.x*B.y-1LL*A.y*B.x;}
int n,m,x[N],y[N],v[N],top;
long long F[M],ans;
long long rk[M];  //记录本次DP第i个题目的优先级 
point rc[M];  //记录到达获得i收益的点(x,y) 

inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}

point DP()
{
	point now;
	now.x = now.y = 4000000;
	for (int i=1;i<=top;i++) F[i] = 1LL << 62;
	F[0] = 0LL;
	for (int i=1;i<=n;i++)
		for (int j=top;j>=v[i];j--) 
			if (F[j] > F[j-v[i]] + rk[i]) {
				rc[j].x = rc[ j-v[i] ].x + x[i];
				rc[j].y = rc[ j-v[i] ].y + y[i];
				F[j] = F[j-v[i]] + rk[i];
			}
	
	int k = m;
	for (int i=m;i<=top;i++) if (F[k] > F[i]) k = i;
	now = rc[k];
	ans = min(ans,1LL*now.x*now.y);
	return now;
}

void solve(point A,point B)
{
	for (int i=1;i<=n;i++) rk[i] = 1LL * x[i] * (A.y - B.y) + y[i] * (B.x - A.x); //更新关键词 
	point C = DP();
	if(Cross(B-A,C-A)>=0) return ;  //寻找不到直线AB左下方的点 
	solve(A,C);solve(C,B); //分成两部分递归 
}

int main()
{
	ans = 1LL << 62; 
    while (~scanf("%d%d",&n,&m))
    {
    	for (int i=1;i<=n;i++) {v[i] = read();x[i] = read(); y[i] = read();}
		for (int i=1;i<=n;i++) top += v[i];
	
		for (int i=1;i<=n;i++) rk[i] = 1LL*x[i];  //DP关键词为横坐标 
		point A = DP(); 						//寻找最靠近y轴的点 
	
		for (int i=1;i<=n;i++) rk[i] = 1LL*y[i];  //DP关键词为纵坐标 
		point B = DP(); 						//寻找最靠近x轴的点 
	
		solve(A,B);  //递归寻找在A,B左下方的点 
	
		printf("%I64d\n",ans);
    }
	return 0;
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值