P1050 · OFFICIAL SOLUTION

P1050 清仓甩卖 官方题解

Gioush OJ · P1050 清仓甩卖

清仓甩卖

【题意简述】

nn 件物品,第 ii 件物品的原价为 wiw_i,清仓价格为 vi{1,2}v_i\in\{1,2\}。在清仓价格总和不超过 mm 的条件下,求能够得到的最大原价总和。

【数据点 1∼41\sim 41∼4】

【题目描述】

这一档保证 n20n\leq 20

【Hint】

直接枚举购买集合。

【解法】

枚举全部 2n2^n 个集合,计算每个集合的清仓价格之和与原价之和,保留总价格不超过 mm 的方案。时间复杂度为 O(n2n)\mathcal{O}(n2^n),空间复杂度为 O(n)\mathcal{O}(n)

【数据点 5∼125\sim 125∼12】

【题目描述】

数据点 585\sim 8 保证所有清仓价格均为 11;数据点 9129\sim 12 保证所有清仓价格均为 22

【Hint】

当所有物品的清仓价格相同时,只需要决定购买数量。

【解法】

若所有清仓价格均为 11,至多购买 min(n,m)\min(n,m) 件物品;若均为 22,至多购买 min(n,m/2)\min(n,\lfloor m/2\rfloor) 件物品。两种情况下都应选择原价最大的若干件。

将原价从大到小排序后取前若干项之和,时间复杂度为 O(nlogn)\mathcal{O}(n\log n)

【数据点 13∼1913\sim 1913∼19】

【题目描述】

这一档同时出现两类物品。

【Hint】

分别对清仓价格为 1122 的物品排序,再枚举购买多少件第二类物品。

【解法】

枚举购买 xx 件清仓价格为 22 的物品,剩余预算为 m2xm-2x,再购买至多 m2xm-2x 件清仓价格为 11 的物品。

直接累加每次选择的物品价值,时间复杂度为 O(n2)\mathcal{O}(n^2)。当前瓶颈只在重复计算两类物品的前若干项之和。

【正解】

【题目描述】

对每种可能的两类物品数量组合求最大价值。

【Hint】

对两类物品分别维护原价的降序前缀和。

【解法】

将清仓价格为 1122 的物品按照原价从大到小排列,分别记为序列 A,BA,B,并维护前缀和 SA,SBS_A,S_B

固定购买 xx 件第二类物品时,选择 BB 的前 xx 件一定不劣。此时还能购买

y=min(A,m2x)y=\min\left(\lvert A\rvert,m-2x\right)

件第一类物品,同样应选择 AA 的前 yy 件。当前方案的价值为

SB(x)+SA(y).S_B(x)+S_A(y).

任意合法方案都对应某个 xx。固定 xx 后,将两类物品分别替换成原价最大的同样件数,不会改变总价格,只会使总价值不减。因此枚举覆盖了某个最优方案。

【复杂度分析】

排序的时间复杂度为 O(nlogn)\mathcal{O}(n\log n),枚举的时间复杂度为 O(n)\mathcal{O}(n),空间复杂度为 O(n)\mathcal{O}(n)。前缀和与答案需要使用 long long

【参考代码】

/*Author:EhundateghDate:2026/7/24Name:sale.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 500010using namespace std;int T,n,m,Value[MAXN],Type[MAXN],One[MAXN],Two[MAXN];long long PreOne[MAXN],PreTwo[MAXN];bool cmp(int a,int b){return a>b;}void Solve(){    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++) scanf("%d",&Value[i]);    for(int i=1;i<=n;i++) scanf("%d",&Type[i]);    int CntOne=0,CntTwo=0;    for(int i=1;i<=n;i++){        if(Type[i]==1) One[++CntOne]=Value[i];        else Two[++CntTwo]=Value[i];    }    sort(One+1,One+CntOne+1,cmp);    sort(Two+1,Two+CntTwo+1,cmp);    PreOne[0]=PreTwo[0]=0;    for(int i=1;i<=CntOne;i++) PreOne[i]=PreOne[i-1]+One[i];    for(int i=1;i<=CntTwo;i++) PreTwo[i]=PreTwo[i-1]+Two[i];    long long Ans=0;    for(int i=0;i<=CntTwo&&i*2<=m;i++){        int Count=min(CntOne,m-i*2);        Ans=max(Ans,PreTwo[i]+PreOne[Count]);    }    printf("%lld\n",Ans);    return;}int main(){    int c;    scanf("%d%d",&c,&T);    while(T-->0) Solve();    return 0;}