P1027 晨汐散余香 官方题解
Gioush OJ · P1027 晨汐散余香
晨汐散余香
【题意简述】
有 个物品,第 个物品价值为 ,最晚在第 天交易。每天至多交易一个物品。对每次询问 ,求最多使用 个交易日时可以得到的最大总价值。
【Hint】
如果只问一次固定天数,价值大的物品应优先考虑,并尽量安排到不晚于截止时间的最晚空闲日。
【提示】
把物品按价值从大到小排序。若当前物品还能放进某个不超过 的空闲日,则选它不会劣于把这个位置留给价值更小的物品。
【解法】
将所有物品按价值从大到小排序。使用并查集维护每个日期左侧最近的空闲位置:若要放入一个截止时间为 的物品,就查询 。若得到的位置非零,则把该物品安排在此处,并将这个位置与左侧位置合并。
按这个过程依次选到第 个物品时,得到的前缀收益就是最多使用 个交易日的最优值。所有询问只需要输出对应前缀即可。
交换证明如下:若某个最优方案中存在一个未选的高价值物品 和一个已选的低价值物品 ,且 可以被放入当前可行日集合中,那么用 替换 不会降低可行性,收益只会增加。因此从高价值到低价值贪心是合法的。
【数据点到正解】
当所有失效时间相同,或所有失效时间都不小于 时,只需把价值排序并取前缀和。一般情形中,固定一次询问后,仍按价值从大到小扫描,把当前物品放在不晚于 的最晚空闲日;这给出了每个询问独立做贪心的做法。
正解注意到扫描顺序从不改变,变化的只有最多选择多少份。因此先不限制选择数量,完整执行一次“价值降序、最晚空闲日”的贪心。扫描到第 个被选物品时,前缀和就是恰好选择 份的最优值:任一可行 元方案在其最小价值物品被扫描到时,都证明此前前缀至少能安排 份;贪心每个前缀都选出尽可能多的物品,故其第 个收益不会更小。
【复杂度】
并查集令被占用的日期指向左侧最近空闲日。排序复杂度为 ,并查集均摊近似 ,回答询问为 。
【参考代码】
/*Author:EhundateghDate:2026/7/21Name:aroma.cppYou steal,I kill.*/#include <cstdio>#include <algorithm>using namespace std;#define MAXN 200010struct item{ int Val,Limit;}Line[MAXN];bool cmp(item a,item b){return a.Val>b.Val;}int n,q,Fa[MAXN],cnt=0,In1;long long Ans[MAXN];int Find(int x){return Fa[x]==x?x:Fa[x]=Find(Fa[x]);}int main(){ freopen("aroma.in","r",stdin); freopen("aroma.out","w",stdout); scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) scanf("%d%d",&Line[i].Val,&Line[i].Limit); sort(Line+1,Line+n+1,cmp); for(int i=0;i<=n;i++) Fa[i]=i; for(int i=1;i<=n;i++){ int Day=Find(min(Line[i].Limit,n)); if(Day==0) continue; Fa[Day]=Find(Day-1); cnt++; Ans[cnt]=Ans[cnt-1]+Line[i].Val; } while(q-->0){ scanf("%d",&In1); printf("%lld\n",Ans[min(In1,cnt)]); } return 0;}