写了两个小时的说,思路和状态转移方程不难想,但是代码调试了好久。刚开始想用三维dp,后来发现自己把自己绕进去了,二维足够(滚动数组应该也可以进一步优化到一维。)

考查:贪心,前缀和,线性dp

状态表示:dp[i][j] 表示第i天,手里有j个糖果时花费的最少费用,因为每天至少吃一个,贪心的想法,每天就吃一个,所以j大于等于1,答案即为dp[n][1]。

转移方程:设第i天买了k个糖果,则


dp[i][j]=min(dp[i][j],dp[i-1][j-k+1]+val[i][k]+k*k);

就是把第i天拥有的j个糖果分成两部分,一部分来自k,另一部分来自i-1天剩下的,所以是j-k+1。

需要注意的是第i-1天可能不能贡献j-k+1个,因为前一天有的最多糖果数比i天少,在这种情况下说明第i天买k个太少了,继续枚举就好,当前新买的糖果要花费的价格可以用前缀和提前处理好,再加上k的平方就好。

上代码。


#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) ((x)&-x)
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
ll val[301][301]={0};
ll dp[301][90001]={0};
void solve(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) cin>>val[i][j];
    }
    for(int i=1;i<=n;i++) sort(val[i]+1,val[i]+m+1);//优先买价格低的。
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) val[i][j]=val[i][j-1]+val[i][j];//前缀和
    }
    for(int i=1;i<=m;i++) dp[1][i]=val[1][i]+i*i;//初始化
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i*m-(i-1)&&j<=n-i+1;j++){//i*m-(i-1)是第i天最多拥有多少糖果,n-i+1是满足条件的最大的j值。
            int w=(i-1)*m-(i-2);//i-1天最多有w个糖果
            dp[i][j]=1e10;
            for(int k=0;k<=j;k++){
                if(j-k+1<=w){
                    dp[i][j]=min(dp[i][j],dp[i-1][j-k+1]+val[i][k]+k*k);
                }
                else{
                    continue;//j-k+1大于w时,说明前一天不能贡献了,需要今天多买一点,所以让k增加。
                }
            }
        }
    }
    cout<<dp[n][1];
}
int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    solve();
    return 0;
}