P8903 [USACO22DEC] Bribing Friends G 题解
思路
$O(N^4)$ 的动态规划朴素做法这里就不再赘述。
我们考虑正解。看数据规模,正解的时间复杂度很有可能是 $O(N^2)$ 的。不难发现,我们这道题比较可能的两种算法是贪心和动态规划。可贪心少个策略,因为去评价一头奶牛有三个指标(受欢迎度、哞尼数以及多少个冰激凌可以化成一个哞尼)。
那么我们考虑继续思考动态规划。怎么优化呢?不难想到排序或者用某种策略以 消掉一维 。可该消哪一维呢?我们发现在固定选哪些奶牛的时候我们的冰激凌会优先用到 $X_i$ 小的奶牛身上。
所以我们发现,按照 $X_i$ 从小到大排序,对每个前缀做动态规划(即从前往后做动态规划),状态 $dp[i][j]$ 表示前 $i$ 头奶牛,共用 $j$ 个冰激凌可以获得的最大人气;相反,我们再从后往前做一遍动态规划,状态 $dp[i][j]$ 表示 $n$ 到 $i$ 这些奶牛,共用 $j$ 个哞尼可以获得的最大人气。那么我们只要枚举中断点,那个中断点哞尼和冰激凌都可以用,然后把两边和中断点相加再取最大值就可以获得最优的方案。
证明
- 会不会有多个中断点呢? 不会。我们考虑把中断点中靠后的点的冰激凌拿去给靠前的,一定不会亏。
- 全选冰激凌和不选冰激凌的区间一定以前后缀的形式分别出现吗? 当然。原因是我们一定会把选的点中 $X_i$ 较大的给 $X_i$ 较小的,这样一定不会比之前的方案差,故为前缀。而后缀是因为都给前面的了。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2010;
struct A{
int p,c,x;
friend bool operator < (const A a,const A b){//x从小到大排序
return a.x<b.x;
}
}a[N];
int n,m[2],ans,dp[2][N][N],mx[2][N][N];
void Init_dp(int t){//dp初始化
memset(dp[t],0xf3,sizeof(dp[t]));
memset(mx[t],0xf3,sizeof(mx[t]));
dp[t][!t?0:n+1][0]=0;
for(int i=0;i<=m[t^1];i++){
mx[t][!t?0:n+1][i]=0;
}
}
void DP(int t){
Init_dp(t);
if(!t){
for(int i=1;i<=n;i++){
int v=a[i].c*a[i].x,w=a[i].p;
for(int j=0;j<=m[t^1];j++){
if(j>=v){
dp[t][i][j]=max(dp[t][i][j],dp[t][i-1][j-v]+w);
}
dp[t][i][j]=max(dp[t][i][j],dp[t][i-1][j]);
mx[t][i][j]=max(mx[t][i][j-1],dp[t][i][j]);
}
}
}
else{
for(int i=n;i>=1;i--){
int v=a[i].c,w=a[i].p;
for(int j=0;j<=m[t^1];j++){
if(j>=v){
dp[t][i][j]=max(dp[t][i][j],dp[t][i+1][j-v]+w);
}
dp[t][i][j]=max(dp[t][i][j],dp[t][i+1][j]);
mx[t][i][j]=max(mx[t][i][j-1],dp[t][i][j]);
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m[0],&m[1]);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].p,&a[i].c,&a[i].x);
}
sort(a+1,a+n+1);
DP(0),DP(1);
for(int i=1;i<=n;i++){
for(int j=0;j<=a[i].c&&j*a[i].x<=m[1];j++){
ans=max(ans,mx[0][i-1][m[1]-a[i].x*j]+a[i].p+mx[1][i+1][m[0]-(a[i].c-j)]);
}
}
printf("%d\n",ans);
return 0;
}