题目链接在这里
题目大意
有N门课,每门课有幸福值h和伤心值c,在你选择的m门课中,假设它们幸福的总和为a,伤心的总和为b,请尽力选择课程使得a*a - a * b - b * b最小。
思路分析
分析上面的式子可以看到,a越大,b越小,整体的值越大。所以我们的思想是,当我们选择的伤心值b确定之后,h越大越好。因为我们考虑到,每门课可以选也可以不选,很容易联想到01背包问题。
对于dp[ i ] [ j ],我们假设是选到i课时,在伤心值为j的情况下,开心值最大为多少。
如果不选这门课的话,开心值dp[ i ] [ j ] = dp[ i - 1 ] [ j ]
如果选的话,开心值dp[i][j] = max(dp[ i ] [ j ], dp[ i - 1 ] [ j - c [ i ] ] + h [ i ] )
这样我们就建立了状态转移方程。
注意伤心值的和最多为50000,所以 j 的取值范围就是0~50000,但是因为i的范围是500,所以dp数组如果是500 * 50000的话就太大了,应该循环利用数组。
代码如下
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int MaxN = 530;
int h[MaxN], c[MaxN];
ll dp[2][50010];
ll cal(ll x, ll y){
return x * x - x * y - y * y;
}
int main(){
ios::sync_with_stdio(false);
ll t, n;
cin >> t;
while(t--){
memset(dp, 0, sizeof(dp));
cin >> n;
int cc = 0;
for(int i = 1; i <= n; ++i){
cin >> h[i] >> c[i];
cc += c[i];
}
int k = 0;
for(int i = 1; i <= n; ++i){ //选第i个
k ^= 1; //循环利用dp数组
for(int j = 0; j <= cc; j++){ //现在的c的和是j
dp[k][j] = dp[k ^ 1][j];
if(c[i] > j) continue;
ll tmp = dp[k ^ 1][j - c[i]] + h[i];
if(tmp > dp[k][j]) dp[k][j] = tmp;
}
}
ll Max = 0;
for(int i = 1; i <= cc; ++i)
Max = max(Max, cal(dp[k][i], i));
cout << Max << endl;
}
return 0;
}