题意
给出nnn个数AiA_iAi
定义排列一个 1~n 的排列 P 的价值为:
∑i=1nAi×Pi\sum_{i=1}^n A_i\times P_ii=1∑nAi×Pi
求出排列价值前kkk小的kkk个排列的价值。
题解
大致思路
价值最小的一定是将最大的AiA_iAi对应最小的PiP_iPi,对AiA_iAi升序排序,使价值为∑Ai×(n−i+1)\sum A_i\times (n-i+1)∑Ai×(n−i+1)
为了方便,我们固定PiP_iPi为 n,n−1,n−2,...,2,1n,n-1,n-2,...,2,1n,n−1,n−2,...,2,1,构造AqiA_{q_i}Aqi使得价值为∑Aqi×(n−i+1)\sum A_{q_i}\times (n-i+1)∑Aqi×(n−i+1)
从最小的价值开始,通过某种构造方法(如交换两个A的元素或者),扩展出其它比当前价值大一点的状态,利用优先队列,选择最小的一个扩展,得到第二小的价值,以此类推,扩展k-1次。
扩展方法
貌似只有这种扩展方法能写,,,
类似于选择排序,假设AtA_tAt及之前的项都已经固定,现在决定第t+1项是什么,假设为AposA_{pos}Apos,将At+1A_{t+1}At+1至Apos−1A_{pos-1}Apos−1的项,往后移一位,给AposA_{pos}Apos腾出空间,然后将AposA_{pos}Apos移动到AtA_tAt的后面。
然后固定的位置就多了一位,即现在的At+1A_{t+1}At+1。
如此操作一次增加的代价为(公式中的AiA_iAi值为操作前的值,后面都是如此)
Apos×(pos−t−1)−∑i=t+1pos−1A[i]=∑i=t+1pos−1A[pos]−A[i]A_{pos}\times (pos-t-1)-\sum_{i=t+1}^{pos-1}A[i]=\sum_{i=t+1}^{pos-1}A[pos]-A[i]Apos×(pos−t−1)−i=t+1∑pos−1A[i]=i=t+1∑pos−1A[pos]−A[i]
设len=pos−t−1len=pos-t-1len=pos−t−1,用 delta(pos,len)delta(pos,len)delta(pos,len)表示将AposA_{pos}Apos向前移动lenlenlen位的价值增加量。
性质
- 这种扩展方法一定能得到A的所有排列。(任何一个排列都可以这样,选一个数->移到相应的位置)
- 按这种扩展方法执行一次之后,未固定的位置仍保持顺序不变。即如果A初始为升序,执行操作后,A未固定的位置仍保持升序。
- delta(pos,len+1)=delta(pos,len)+Apos−Apos−len−1delta(pos,len+1)=delta(pos,len)+A_{pos}-A_{pos-len-1}delta(pos,len+1)=delta(pos,len)+Apos−Apos−len−1,又因为AAA的未固定位置为升序,所以delta(pos,len+1)>=delta(pos,len)delta(pos,len+1)>=delta(pos,len)delta(pos,len+1)>=delta(pos,len)
维护
需要使用可持久化线段树。
初始时将每个pospospos的lenlenlen设为1(这样最小),deltadeltadelta即为Apos−Apos−1A_{pos}-A_{pos-1}Apos−Apos−1(第一位为INF)
使用线段树,每次可以快速找到最小的deltadeltadelta值
并且用线段树维护A的哪些位置已经被固定了。
执行一次扩展操作,需要将A1A_1A1~Apos−len−1A_{pos-len-1}Apos−len−1以及AposA_{pos}Apos标记为已固定,并且将它们的deltadeltadelta值设为INF(并不需要真的把AposA_{pos}Apos移到前面去),以后的操作都要将已固定的位置忽略。然后还要将第一个未被固定的位置的deltadeltadelta设为INF。
实现时,线段树上用restrestrest表示当前区间还有restrestrest个AiA_iAi没有被固定,很容易维护。对于区间标记,把区间找到设为空节点即可(显然都是左儿子)。对于AposA_{pos}Apos的标记,单点修改即可。
计算新的扩展
有两种新的扩展:
- 执行完delta(pos,len)delta(pos,len)delta(pos,len)之后的新扩展,则获得一个新的A序列,此时所有的剩下位置len全部重新设为1,计算deltadeltadelta。再可持久化线段树上,用initinitinit记录刚计算出delta(pos,len)delta(pos,len)delta(pos,len)时的线段树状态,与执行delta(pos,len)delta(pos,len)delta(pos,len)的操作后的操作对比,容易发现,这两个棵线段树的区别只有一大堆A被标记,和delta(pos+1)delta(pos+1)delta(pos+1)变化。所以可以利用可持久化线段树,从initinitinit开始做标记,修改等操作。将delta(pos+1,1)delta(pos+1,1)delta(pos+1,1)修改为Apos+1−Apos−1A_{pos+1}-A_{pos-1}Apos+1−Apos−1后,标记固定的A,此时lenlenlen全部本来就是1,不需要修改。
- 将pospospos位置的lenlenlen修改为len+1len+1len+1,并且计算新的delta(pos)delta(pos)delta(pos)。用nownownow记录当前状态的线段树,只需在nownownow上进行单点修改即可。
总结实现
线段树上维护restrestrest(剩余可用的A数量),deltadeltadelta,pospospos,lenlenlen(区间中最小的扩展)
优先队列存储结构体StateStateState
StateStateState里存储两棵线段树的根: initinitinit,nownownow,和一个答案ansansans,为initinitinit时的答案
优先队列的比较以ans+now−>deltaans+now->deltaans+now−>delta为关键字,选最小值。
进行第一种方法扩展状态时,建立新的StateStateState,从旧的StateStateState的initinitinit根转移,得到新的线段树,设为新StateStateState的initinitinit和nownownow,并将新StateStateState的ansansans设为旧StateStateState的ans+now−>ansans+now->ansans+now−>ans,然后将此状态放入优先队列
进行第二种方法扩展状态时,直接在当前的nownownow线段树上修改,然后再放回优先队列即可。
代码
变量名均与题解相同
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const long long LLF=0x3F3F3F3F3F3F3F3FLL;
const int MAXN=100005,MAXLOG=17;
int N,K,A[MAXN];
struct Node
{
int pos,len,rest;
long long delta;
Node *son[2];
};
struct State
{
Node *init,*now;
long long ans;
State(){}
State(Node *i,Node *n,long long a):init(i),now(n),ans(a){}
bool operator > (const State &t)const
{return ans+now->delta>t.ans+t.now->delta;}
};
namespace SegmentTree
{
Node nodes[MAXN*MAXLOG*10],*nd_it=nodes;
int Rest(Node *u)
{return u==NULL?0:u->rest;}
void PushUp(Node *u)
{
Node *l=u->son[0],*r=u->son[1];
u->rest=Rest(l)+Rest(r);
if(l&&l->delta<r->delta)
u->delta=l->delta,u->pos=l->pos,u->len=l->len;
else
u->delta=r->delta,u->pos=r->pos+Rest(l),u->len=r->len;
}
void Build(Node *&u,int L=1,int R=N)
{
u=nd_it++;
if(L==R)
{
u->pos=u->len=u->rest=1;
u->delta=L>1?A[L]-A[L-1]:LLF;
return;
}
int mid=(L+R)/2;
Build(u->son[0],L,mid);
Build(u->son[1],mid+1,R);
PushUp(u);
}
void Add(Node *&res,Node *u,int id,long long ad,int L=1,int R=N)
{
res=nd_it++;
memcpy(res,u,sizeof(Node));
if(L==R)
{
res->delta+=ad;
res->len++;
return;
}
int mid=(L+R)/2;
if(id<=Rest(u->son[0]))
Add(res->son[0],u->son[0],id,ad,L,mid);
else
Add(res->son[1],u->son[1],id-Rest(u->son[0]),ad,mid+1,R);
PushUp(res);
}
void Modify(Node *&res,Node *u,int id,int exist,long long nw,int L=1,int R=N)
{
res=nd_it++;
memcpy(res,u,sizeof(Node));
if(L==R)
{
res->rest=exist;
res->delta=nw;
return;
}
int mid=(L+R)/2;
if(id<=Rest(u->son[0]))
Modify(res->son[0],u->son[0],id,exist,nw,L,mid);
else
Modify(res->son[1],u->son[1],id-Rest(u->son[0]),exist,nw,mid+1,R);
PushUp(res);
}
void Delete(Node *&res,Node *u,int id,int L=1,int R=N)
{
res=nd_it++;
memcpy(res,u,sizeof(Node));
int mid=(L+R)/2;
if(id<Rest(u->son[0]))
Delete(res->son[0],u->son[0],id,L,mid);
else
{
res->son[0]=NULL;
if(id>Rest(u->son[0]))
Delete(res->son[1],u->son[1],id-Rest(u->son[0]),mid+1,R);
}
PushUp(res);
}
long long Val(Node *u,int id,int L=1,int R=N)
{
if(L==R)
return A[L];
int mid=(L+R)/2;
if(id<=Rest(u->son[0]))
return Val(u->son[0],id,L,mid);
return Val(u->son[1],id-Rest(u->son[0]),mid+1,R);
}
}
priority_queue<State,vector<State>,greater<State>> Q;
void GetNewState(State u)
{
using namespace SegmentTree;
Node *nw=u.init,*tmp;
int pos=u.now->pos,len=u.now->len;
if(pos+1<=nw->rest)
{
long long nwval=Val(nw,pos+1)-Val(nw,pos-1);
Modify(tmp,nw,pos+1,1,nwval),nw=tmp;
}
Modify(tmp,nw,pos,0,LLF),nw=tmp;
if(pos-len>1)
Delete(tmp,nw,pos-len-1),nw=tmp;
Modify(tmp,nw,1,1,LLF),nw=tmp;
Q.push(State(nw,nw,u.ans+u.now->delta));
nw=u.now;
if(pos-len>1)
{
long long nwval=Val(nw,pos)-Val(nw,pos-len-1);
Add(tmp,nw,pos,nwval),nw=tmp;
}
else
Modify(tmp,nw,pos,1,LLF),nw=tmp;
Q.push(State(u.init,nw,u.ans));
}
int main()
{
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++)
scanf("%d",&A[i]);
sort(A+1,A+N+1);
long long sum=0;
for(int i=1;i<=N;i++)
sum+=1LL*A[i]*(N-i+1);
printf("%lld\n",sum);
State u;
SegmentTree::Build(u.init);
u.now=u.init;
u.ans=sum;
Q.push(u);
for(int i=1;i<K;i++)
{
u=Q.top();
Q.pop();
printf("%lld\n",u.ans+u.now->delta);
GetNewState(u);
}
return 0;
}