题目描述
给定一个包含n个整数的数组a。定义一个操作如下:
从数组a中选择k个整数,将它们删除,并将它们的和追加到数组末尾。
如果数组A比数组B(长度相同)字典序大,那么在A和B第一次不同的位置上,A的数字比B对应位置上的数字要大。例如,[0, 1, 14, 0]比[0, 1, 5, 6]字典序大,因为它们在第三个数字上第一次不同,而14比5大。字典序最大的数组意味着该数组在长度相同的情况下比其他任何数组都要大。
请输出在执行给定操作p次后的字典序最大的数组。
题解
考虑到每次合并会相当于减少k个数,并在末尾增加1个数。则数组会减少k-1个数。
在执行p次操作后,剩余的数字个数为rest = n -(k-1)* p。没有参与合并操作的数字个数为old = max(0,n - k *p)。
现在分别处理不参与合并的数列和参与合并的数列。
- 不参与合并:相当于计算原数列长度为k的最大字典序子序列。
- 设查找范围为(l,r),查找数列长度为k。
- 每次先找到该范围的一个最大值(val)及最大值的下标(pos),用线段树维护。
- 若r - pos > k - 1,即在该最大值之后选数可以选到k个,便在该位置之后递归。
- 否则,该最大值之后的数全部选择也达不到k个,就需要在该位置之前选k - 1 - (r - pos)个数,并在其之后选r - pos个数。
- 由于题目要求字典序最大,可知所有合并的数组成的数列一定是单调不上升的。因为一旦有数大于在它前面的数,那么把二者互换位置就可以使字典序更大。
因此可以通过排序和堆,先将大的数合并放到较前的位置,小的数合并放到后面的位置。
需要特殊考虑合并生成的第一个数:该数可能不止由k个数合并而成,若p值较大,第一个数可能经历多次合并,由(k - 1)* re + 1个数合并得到,re = p - (rest - old)+ 1。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+7;
int n,k,p;
int a[N],v[N];
priority_queue<int>q;
struct MaxNumber{
int pos,val;
};
struct SegmentTree{
int l,r;
MaxNumber num;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define num(x) tree[x].num
#define val(x) tree[x].num.val
#define pos(x) tree[x].num.pos
}tree[N<<2];
void build(int p,int l,int r){
l(p)=l,r(p)=r;
if(l==r){val(p)=a[l];pos(p)=l;return;}
int mid=l+r>>1;
build(p*2,l,mid),build(p*2+1,mid+1,r);
if(val(p*2)>=val(p*2+1))val(p)=val(p*2),pos(p)=pos(p*2);
else val(p)=val(p*2+1),pos(p)=pos(p*2+1);
}
MaxNumber ask(int p,int l,int r){
if(l<=l(p)&&r>=r(p))return num(p);
int mid=l(p)+r(p)>>1;
MaxNumber L,R;
L.val=0,R.val=0;
if(l<=mid)L=ask(p*2,l,r);
if(r>mid)R=ask(p*2+1,l,r);
if(L.val>=R.val)return L;
else return R;
}
void check(int l,int r,int cnt){
if(l>r)return;
MaxNumber x=ask(1,l,r);
v[x.pos]=1;
if(cnt<=1)return;
int behind=r-x.pos;
if(behind>=cnt-1)check(x.pos+1,r,cnt-1);
else{
int front=cnt-1-behind;
check(l,x.pos-1,front);
check(x.pos+1,r,behind);
}
}
void clear(){
for(int i=1;i<=n;i++)a[i]=v[i]=0;
while(q.size())q.pop();
}
signed main(){
int t;
cin>>t;
while(t--){
int sum=0;
cin>>n>>k>>p;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
if(n==(k-1)*p+1){
cout<<sum<<endl;
continue;
}
int rest=n-(k-1)*p;
int old=max(n-p*k,0ll);
int ne=rest-old;
build(1,1,n);
if(old>0)check(1,n,old);
for(int i=1;i<=n;i++){
if(v[i])cout<<a[i]<<" ";
else q.push(a[i]);
}
int re=p-ne+1;
int first=0;
for(int i=1;i<=re*(k-1)+1;i++){
int x=q.top();
q.pop();
first+=x;
}
cout<<first<<" ";
for(int i=2;i<=ne;i++){
int ans=0;
for(int j=1;j<=k;j++){
int x=q.top();
q.pop();
ans+=x;
}
cout<<ans<<" ";
}
cout<<endl;
clear();
}
}