https://ac.nowcoder.com/acm/contest/886/J
题意:n个技能,m个技能,升级每个技能每一级可能支出,也可能获得收益,当n个技能同时都升到某一等级,还会有福利,可能额外支出,也可能获得额外收益,现在让你求最大收益。
题解:数据小,可暴力,否则dp; 定义支出为负,收益为正
首先明白官方题解:一种错误的想法是枚举有 j 个 level 升满,然后对第 i 种科技从 pre[i][j] 到 pre[i][m] 中选最小的那个作为第 i 种科技的最终 level(这里 pre[i][j] 表示第 i 种科技升前 j 个 level 的代 价和) 这种做法的问题在于 dj 可能是负的,贪心选取最小的 pre 可能导致 j+1 之后某些负的 level 使答案变差。很不幸,我在这里死活没有跳出来,枚举前J个技能升满后,贪心找到的不是最优情况,会受到 枚举到的J~具体升到级数I之间的额外福利影响。
所以稍微修改一下,从后往前扫预处理出最优1~J升满后,后面(J~M)升级的最优,保证不收到1~J的额外福利影响。
for(int i=1;i<=n;i++){//记录枚举的共同等级后面自由升级的最优情况
for(int j=m;j>=1;j--){
if(j==m){
mx[i][j]=a[i][j];
}else{
mx[i][j]=max(mx[i][j+1]+a[i][j],a[i][j]);
}
}
}
接下来就是暴力枚举共同升级的前J个级数的收益+后面的最优收益(I级),会出现这种情况,就是这N个技能后面最优收益级数都大于枚举的J,会多出J~I的福利影响,所以需要把这N个技能后面最优收益最小的内个级数让他就升到J级。
后面的最优收益会有三种情况:1.全正(减去最小正收益即可,只升到J级)2.全负(他们都只升到J级)3.有正有负(只考虑正数收益即可)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e3 + 5;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll MOD = 1e9 + 7;
ll a[maxn][maxn];
ll sum[maxn][maxn];
ll pre[maxn];
ll mx[maxn][maxn];
int main(){
std::ios::sync_with_stdio(false);
int t;
cin>>t; int kase=1;
while(t--){
memset(a, 0, sizeof(a));
memset(sum, 0, sizeof(sum));
memset(pre, 0, sizeof(pre));
memset(mx, 0, sizeof(mx));
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){//每行前缀和
for(int j=1;j<=m;j++){
cin>>a[i][j];a[i][j]=-a[i][j];
sum[i][j]=sum[i][j-1]+a[i][j];
}
}
for(int i=1;i<=m;i++){//额外福利前缀和
cin>>pre[i];
pre[i]=pre[i-1]+pre[i];
}
for(int i=1;i<=n;i++){//记录枚举的共同等级后面自由升级的最优情况
for(int j=m;j>=1;j--){
if(j==m){
mx[i][j]=a[i][j];
}else{
mx[i][j]=max(mx[i][j+1]+a[i][j],a[i][j]);
}
}
}
ll ans=0;
for(int i=0;i<m;i++){
ll mi=mx[1][i+1]; ll tmp=0; ll psum=0;
for(int j=1;j<=n;j++){
if(mx[j][i+1]>0) tmp+=mx[j][i+1];
mi=min(mx[j][i+1],mi);//最小收益
psum+=sum[j][i];
}
if(mi<0) ans=max(ans,tmp+psum+pre[i]);
else ans=max(ans,tmp+psum+pre[i]-mi);
}
ll qw=0;
for(int i=1;i<=n;i++){
qw+=sum[i][m];
}
ans=max(qw+pre[m],ans);
cout<<"Case #"<<kase++<<": "<<ans<<endl;
}
return 0;
}