题目描述
大赛将至,摆在你面前的是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);
}