[51nod 1614]刷题计划

题目描述

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

最小乘积生成树

把每种方案的代码量和当做横坐标,无聊值和当做纵坐标,每种方案都可以用一个二维平面的点表示。
首先同在一个反比例函数上的点横纵乘积相同,我们要找一个点横纵乘积最小。
首先用01背包求出符合条件(条件指要满足智力值的下限)横坐标最小的点和纵坐标最小的点。
记为(x1,y1)和(x2,y2)
找到离这两点连线段距离最远的在其左下方的点。
设dx=|x1-x2|,dy=|y1-y2|,斜率k=dy/dx
那么对于(x,y),我们用过该点的斜率为k的直线与y轴的交点大小来刻画其打连线段距离的远近。
那么交点大小为kx+y
现在要找到这个估量值最小的点
返回来看第i道题目,对答案贡献为dy/dxb[i]+c[i]
为了方便,转化为整数dyb[i]+c[i]dx
设d[i]表示这个估量值,然后做01背包,于是我们又找到一个点C
这里写图片描述
显然三角形内的点都不会比C优,因为其所处反比例函数一定不比C所在反比例函数更靠近原点。
接下来递归AC和BC继续计算。
复杂度不会证……

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=1600+10;
ll f[maxn][maxn],g[maxn][maxn];
ll a[maxn],b[maxn],c[maxn],d[maxn];
ll i,j,k,l,t,n,m,ans,x,y,x1,y1,x2,y2,dx,dy;
void dp(){
    int i,j,k;
    fo(i,0,n)
        fo(j,0,800)
            f[i][j]=1000000000000;
    f[0][0]=0;
    fo(i,0,n-1)
        fo(j,0,800)
            if (f[i][j]!=1000000000){
                if (f[i][j]+d[i+1]<f[i+1][j+a[i+1]]){
                    f[i+1][j+a[i+1]]=f[i][j]+d[i+1];
                    g[i+1][j+a[i+1]]=1;
                }
                if (f[i][j]<f[i+1][j]){
                    f[i+1][j]=f[i][j];
                    g[i+1][j]=0;
                }
            }
    k=-1;
    fo(i,m,800)
        if (f[n][i]!=1000000000&&(k==-1||f[n][i]<f[n][k])) k=i;
    x=y=0;
    fd(i,n,1){
        if (g[i][k]){
            k-=a[i];
            x+=b[i];
            y+=c[i];
        }
    }
}
void solve(ll x1,ll y1,ll x2,ll y2){
    dx=abs(x1-x2);
    dy=abs(y1-y2);
    ll i;
    fo(i,1,n) d[i]=b[i]*dy+c[i]*dx;
    dp();
    ll xx=x,yy=y;
    if ((xx==x1&&yy==y1)||(xx==x2&&yy==y2)) return;
    else{
        ans=min(ans,xx*yy);
        solve(x1,y1,xx,yy);
        solve(xx,yy,x2,y2);
    }
}
int main(){
    scanf("%lld%lld",&n,&m);
    fo(i,1,n) scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
    fo(i,1,n) d[i]=b[i];
    dp();
    x1=x;y1=y;
    fo(i,1,n) d[i]=c[i];
    dp();
    x2=x;y2=y;
    ans=min(x1*y1,x2*y2);
    solve(x1,y1,x2,y2);
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值