2024-06 第34次 ccf-csp T4 货物调度 100pts 题解

 先自我介绍一下,本人大一,计算机专业本科在读,大学0基础自学算法,无信息竞赛基础,实力低微,这是本人第一次写题解来记录自己做题时的感悟,还是比较紧张的QWQ~

由于实力不济,思路上可能会有很多疏漏,希望各路大神不吝赐教!

一、题目描述

二、思路分析 

 这题是一道比较明显的dp,算法本身是不难确定的,但应如何确立状态函数的两个维度呢?第一想法是用dp[i][j]表示前i个货物在j的代价下能够获取的净收入最大值,但显然行不通,因为在推演过程中无法确定货物对应仓库的编号是否已经使用过,这将是最大的障碍,一时间有点摸不清头脑。

然后我又想到了分组dp再集中处理,但也没有可行思路,反而把问题越想越复杂了。

最后,我转念一想,这题的主角不应该是“货物”,而应该是“仓库”。因为仓库中的货物无论先取哪一个,只要取的数目相同,代价都是一样的,利用这一特性,我们可以弱化“货物”这个概念,就单纯去想每一个仓库要怎么取最优,就好了!所以为了获取最大净收入,我可以直接把货物扔仓库并进行降序排序,这样在每一个仓库中从大到小依次取,找出整体最大值再合并,就是我们要的状态转移了。

三、实现过程和注意事项

1.用一个vector数组a来表示货物价格,a[i][j]表示第i个仓库降序第j的货物的价格;

2.设置状态函数。dp[i][j]表示前i个仓库,以不高于j的费用调度货物,所能达到的净利润最大值;

3.思考状态转移方程。如果不选取第i个仓库,那么dp[i][j]=dp[i-1][j];如果选择k个货物,那么dp[i][j]=dp[i-1][j-b[i]-c[i]*k]+(\sum_{l=1}^{k}a[i][l])-b[i]-c[i]*k.最后将所有范围内的这一转移结果取max即可;

4.注意范围:k<=a[i].size()&&j-b[i]-c[i]*k>=0;

5.初始化dp[0][i]=0.

四、总结

对于dp,大部分像我这种初学者比较困惑的一点就是——维度的含义怎么设置,状态转移方程怎么确立,以及如何初始化之类的问题。通过多练习把这些问题搞懂,结合具体问题具体分析,dp题自然就迎刃而解了。

通过这道题,我们可以获得一些启发——dp没有思维定式,不要想当然地认为就应该去转移货物,殊不知这是命题人的高级障眼法罢了。

五、满分代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
//csp34-4 货物调度
int dp[1005][40010];//前i个仓库费用j可以获得的最大总价值
vector<int>a[1005];//第i个仓库的第j个货物的价值
int b[1005],c[1005];//仓库属性
bool dcmp(int a,int b){
    return a>b;
}
int main(){
    int n,m,v;
    cin>>n>>m>>v;
    for(int i=0;i<=40000;i++)dp[0][i]=0;
    for(int i=1;i<=n;i++){
        cin>>b[i]>>c[i];
    }
    for(int i=1;i<=m;i++){
        int val,t;
        cin>>val>>t;
        t++;
        a[t].push_back(val);
    }
    for(int i=1;i<=n;i++){
        sort(a[i].begin(),a[i].end(),dcmp);
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=40000;j++){
            dp[i][j]=dp[i-1][j];//不选i
            int sum=0;
            for(int k=0;k<a[i].size();k++){//选i
                if(b[i]+c[i]*(k+1)>j)break;
                sum+=a[i][k];//收益和
                dp[i][j]=max(dp[i][j],dp[i-1][j-b[i]-c[i]*(k+1)]+sum-b[i]-c[i]*(k+1));
            }
        }
    }
    int ans;
    for(int i=0;i<=40000;i++){
        if(dp[n][i]>=v){
            ans=i;break;
        }
    }
    cout<<ans;
    return 0;
}

 需要说明的一点是,我没有进行降维处理,因为数据范围不是很大,写出来一次就过了也就没进行修改。也不麻烦,只是要注意j从后往前转移即可。

下面是评测记录:

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值