P1046 · OFFICIAL SOLUTION

P1046 抢修计划 官方题解

Gioush OJ · P1046 抢修计划

抢修计划

【题意简述】

nn 个依次开放的区域。区域 11 始终可以抢修;只有区域 1,2,,i11,2,\ldots,i-1 均至少抢修过一次,区域 ii 才能被抢修。首次抢修区域 ii 获得 aia_i,以后每次获得 bib_i。最多进行 kk 次行动,求最大总价值。

【Hint】

【提示】

假设最终只开放到区域 ii。前 ii 个区域必须各完成一次首次抢修,剩余行动全部放在 bjb_j 最大的已开放区域上。

【数据点 1∼51\sim 51∼5】

n,k10n,k\leq 10 时,可以把一次抢修看成搜索树的一层。状态记录各区域已经抢修的次数,枚举当前已经开放的区域作为下一次选择。同一状态只保留已经得到的最大价值,搜索深度不超过 kk

这一做法完整枚举了全部合法行动序列,可以作为后续结论的直接验证。

【数据点 6∼106\sim 106∼10】

假设最后开放到区域 ii。区域 1,2,,i1,2,\ldots,i 都必须完成一次首次抢修,所以已经消耗 ii 次行动,得到

j=1iaj.\sum_{j=1}^{i}a_j.

剩余 kik-i 次行动不会再改变开放范围。每次都选择 bjb_j 最大的已开放区域一定最优,因此候选答案为

j=1iaj+(ki)max1jibj.\sum_{j=1}^{i}a_j+(k-i)\max_{1\leq j\leq i}b_j.

直接枚举 ii 并重新计算前缀最大值,时间复杂度为 O(n2)\mathcal{O}(n^2)

【数据点 11∼1511\sim 1511∼15】

特殊性质保证 bb 单调不降。最后开放到区域 ii 时,已经开放区域中的最大重复抢修价值就是 bib_i,于是候选答案变为

j=1iaj+(ki)bi.\sum_{j=1}^{i}a_j+(k-i)b_i.

顺序维护 aa 的前缀和即可在线性时间完成这一档。

【正解】

定义

Ai=j=1iaj,Bi=max1jibj.A_i=\sum_{j=1}^{i}a_j, \qquad B_i=\max_{1\leq j\leq i}b_j.

最后开放到区域 ii 时,最优价值为

Ai+(ki)Bi,A_i+(k-i)B_i,

其中 1imin(n,k)1\leq i\leq\min(n,k)

必要性是显然的:想要开放区域 ii,必须依次完成前 ii 个区域的首次抢修,因此前 ii 次行动的总价值固定为 AiA_i;剩余每次行动的价值不超过 BiB_i

充分性同样成立:依次完成前 ii 个区域的首次抢修,再把剩余 kik-i 次行动全部放在某个取得 BiB_i 的区域上,恰好得到上述价值。因此枚举 ii 已经覆盖全部最优方案。

顺序扫描时同时维护 Ai,BiA_i,B_i,对每个合法的 ii 更新答案即可。

【复杂度分析】

时间复杂度为 O(n)\mathcal{O}(n),空间复杂度为 O(n)\mathcal{O}(n)。总价值需要使用 long long

【参考代码】

/*Author:EhundateghDate:2026/7/30Name:repair.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 200010using namespace std; int c,T,n,k;long long a[MAXN],b[MAXN]; void Solve(){    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);    for(int i=1;i<=n;i++) scanf("%lld",&b[i]);    long long Ans=0,PreSum=0,Max=0;    for(int i=1;i<=min(n,k);i++){        PreSum+=a[i];        Max=max(Max,b[i]);        Ans=max(Ans,PreSum+1ll*(k-i)*Max);    }    printf("%lld\n",Ans);} int main(){    scanf("%d%d",&c,&T);    while(T-->0) Solve();    return 0;}