CF1870D Prefix Purchase 题解
CF1870D Prefix Purchase 题解
题意
你有一个长为 \(n\) 的数组 \(a\),初始全为 \(0\)。
初始你有 \(k\) 个硬币。你可以花费 \(c_i\) 个硬币让 \(a\) 数组 \([1,i]\) 的前缀的值 \(+1\)。你要保证自己的硬币数量始终非负。
问 \(a\) 数组的字典序最大是多少。
思路
萌新竟然能切 D 了!!!
首先考虑 dp。记 \(f_i\) 表示花费 \(i\) 个硬币时,\(a_1\) 最大是多少。但是要最大化字典序,所以我们需要尽可能选 \(i\) 较大的 \(c_i\)。所以我们记 \(g_i\) 表示 \(f_i\) 是用哪种硬币转移过来的。跑完全背包即可。复杂度 \(O(nk)\)。
发现这样复杂度过于炸裂。我们考虑 dp 做的是什么,就是优先选尽可能多的 \(a_1\),然后尽可能多的选择 \(a_2\),以此类推。
考虑如何选尽可能多的 \(a_1\)。肯定是选择全局最小值啊!考虑如何选尽可能多的 \(a_2\)。肯定是用后缀最小值替换掉上面对 \(a_1\) 贡献的一些 \(c\) 啊!所以我们用堆维护已经选过的 \(c\),每次用 \(c\) 的后缀最小值去尝试替换。
复杂度 \(O(n\log n)\)。
代码
萌新代码有点丑,大佬不要嫌弃
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int ans=0;bool op=0;char ch=getchar();
while(ch<'0'||'9'<ch){if(ch=='-')op=1;ch=getchar();}
while('0'<=ch&&ch<='9'){ans=(ans<<1)+(ans<<3)+(ch^48);ch=getchar();}
if(op)return -ans;
return ans;
}
const int maxn=2e5+10;
int n,k;
int c[maxn];
int mn[maxn];//c的后缀最小值的下标
int a[maxn];//最终形成的序列
struct node{
int val,cnt,id;//这个c的权值、数量、编号
bool operator < (const node &cmp)const{
return val<cmp.val||(val==cmp.val&&id>cmp.id);
}
};
priority_queue<node>q;
void real_main(){
//read
n=read();
for(int i=1;i<=n;i++)c[i]=read();
k=read();
//init
//清空
while(!q.empty())q.pop();
memset(a,0,sizeof(int)*(n+2));
mn[n]=n;
for(int i=n-1;i>=1;i--)mn[i]=c[i]<c[mn[i+1]]?i:mn[i+1];//处理后缀最小值的下标
q.push((node){0,(int)2e9,0});//加一个0,方便处理
//solve
for(int i=1;i<=n;i++){
node awa=(node){c[mn[i]],0,mn[i]};
while(!q.empty()){
node now=q.top();q.pop();
int dif=c[mn[i]]-now.val;
if(!dif){//如果不用替换,那么直接退出
q.push(now);//拿出来的没有动,放回去
break;
}
int tmp=min(k/dif,now.cnt);//计算一下可以替换掉几个
now.cnt-=tmp;
awa.cnt+=tmp;
a[now.id]-=tmp;
a[mn[i]]+=tmp;
k-=dif*tmp;
if(now.cnt)q.push(now);
if(!tmp)break;//已经不能替换了,退出
}
if(awa.cnt)q.push(awa);
}
for(int i=n;i>=1;i--)a[i]=a[i+1]+a[i];//我们的a是一个后缀和,算出来
for(int i=1;i<=n;i++)cout<<a[i]<<' ';
puts("");
}
signed main(){
int T=read();//多组数据
while(T--)real_main();
return 0;
}s

浙公网安备 33010602011771号