题意
给出
n
n
n个数
A
i
A_i
Ai
定义排列一个 1~n 的排列 P 的价值为:
∑
i
=
1
n
A
i
×
P
i
\sum_{i=1}^n A_i\times P_i
i=1∑nAi×Pi
求出排列价值前
k
k
k小的
k
k
k个排列的价值。
题解
大致思路
价值最小的一定是将最大的
A
i
A_i
Ai对应最小的
P
i
P_i
Pi,对
A
i
A_i
Ai升序排序,使价值为
∑
A
i
×
(
n
−
i
+
1
)
\sum A_i\times (n-i+1)
∑Ai×(n−i+1)
为了方便,我们固定
P
i
P_i
Pi为
n
,
n
−
1
,
n
−
2
,
.
.
.
,
2
,
1
n,n-1,n-2,...,2,1
n,n−1,n−2,...,2,1,构造
A
q
i
A_{q_i}
Aqi使得价值为
∑
A
q
i
×
(
n
−
i
+
1
)
\sum A_{q_i}\times (n-i+1)
∑Aqi×(n−i+1)
从最小的价值开始,通过某种构造方法(如交换两个A的元素或者),扩展出其它比当前价值大一点的状态,利用优先队列,选择最小的一个扩展,得到第二小的价值,以此类推,扩展k-1次。
扩展方法
貌似只有这种扩展方法能写,,,
类似于选择排序,假设
A
t
A_t
At及之前的项都已经固定,现在决定第t+1项是什么,假设为
A
p
o
s
A_{pos}
Apos,将
A
t
+
1
A_{t+1}
At+1至
A
p
o
s
−
1
A_{pos-1}
Apos−1的项,往后移一位,给
A
p
o
s
A_{pos}
Apos腾出空间,然后将
A
p
o
s
A_{pos}
Apos移动到
A
t
A_t
At的后面。
然后固定的位置就多了一位,即现在的
A
t
+
1
A_{t+1}
At+1。
如此操作一次增加的代价为(公式中的
A
i
A_i
Ai值为操作前的值,后面都是如此)
A
p
o
s
×
(
p
o
s
−
t
−
1
)
−
∑
i
=
t
+
1
p
o
s
−
1
A
[
i
]
=
∑
i
=
t
+
1
p
o
s
−
1
A
[
p
o
s
]
−
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]
设
l
e
n
=
p
o
s
−
t
−
1
len=pos-t-1
len=pos−t−1,用
d
e
l
t
a
(
p
o
s
,
l
e
n
)
delta(pos,len)
delta(pos,len)表示将
A
p
o
s
A_{pos}
Apos向前移动
l
e
n
len
len位的价值增加量。
性质
- 这种扩展方法一定能得到A的所有排列。(任何一个排列都可以这样,选一个数->移到相应的位置)
- 按这种扩展方法执行一次之后,未固定的位置仍保持顺序不变。即如果A初始为升序,执行操作后,A未固定的位置仍保持升序。
- d e l t a ( p o s , l e n + 1 ) = d e l t a ( p o s , l e n ) + A p o s − A p o s − l e n − 1 delta(pos,len+1)=delta(pos,len)+A_{pos}-A_{pos-len-1} delta(pos,len+1)=delta(pos,len)+Apos−Apos−len−1,又因为 A A A的未固定位置为升序,所以 d e l t a ( p o s , l e n + 1 ) > = d e l t a ( p o s , l e n ) delta(pos,len+1)>=delta(pos,len) delta(pos,len+1)>=delta(pos,len)
维护
需要使用可持久化线段树。
初始时将每个
p
o
s
pos
pos的
l
e
n
len
len设为1(这样最小),
d
e
l
t
a
delta
delta即为
A
p
o
s
−
A
p
o
s
−
1
A_{pos}-A_{pos-1}
Apos−Apos−1(第一位为INF)
使用线段树,每次可以快速找到最小的
d
e
l
t
a
delta
delta值
并且用线段树维护A的哪些位置已经被固定了。
执行一次扩展操作,需要将
A
1
A_1
A1~
A
p
o
s
−
l
e
n
−
1
A_{pos-len-1}
Apos−len−1以及
A
p
o
s
A_{pos}
Apos标记为已固定,并且将它们的
d
e
l
t
a
delta
delta值设为INF(并不需要真的把
A
p
o
s
A_{pos}
Apos移到前面去),以后的操作都要将已固定的位置忽略。然后还要将第一个未被固定的位置的
d
e
l
t
a
delta
delta设为INF。
实现时,线段树上用
r
e
s
t
rest
rest表示当前区间还有
r
e
s
t
rest
rest个
A
i
A_i
Ai没有被固定,很容易维护。对于区间标记,把区间找到设为空节点即可(显然都是左儿子)。对于
A
p
o
s
A_{pos}
Apos的标记,单点修改即可。
计算新的扩展
有两种新的扩展:
- 执行完 d e l t a ( p o s , l e n ) delta(pos,len) delta(pos,len)之后的新扩展,则获得一个新的A序列,此时所有的剩下位置len全部重新设为1,计算 d e l t a delta delta。再可持久化线段树上,用 i n i t init init记录刚计算出 d e l t a ( p o s , l e n ) delta(pos,len) delta(pos,len)时的线段树状态,与执行 d e l t a ( p o s , l e n ) delta(pos,len) delta(pos,len)的操作后的操作对比,容易发现,这两个棵线段树的区别只有一大堆A被标记,和 d e l t a ( p o s + 1 ) delta(pos+1) delta(pos+1)变化。所以可以利用可持久化线段树,从 i n i t init init开始做标记,修改等操作。将 d e l t a ( p o s + 1 , 1 ) delta(pos+1,1) delta(pos+1,1)修改为 A p o s + 1 − A p o s − 1 A_{pos+1}-A_{pos-1} Apos+1−Apos−1后,标记固定的A,此时 l e n len len全部本来就是1,不需要修改。
- 将 p o s pos pos位置的 l e n len len修改为 l e n + 1 len+1 len+1,并且计算新的 d e l t a ( p o s ) delta(pos) delta(pos)。用 n o w now now记录当前状态的线段树,只需在 n o w now now上进行单点修改即可。
总结实现
线段树上维护
r
e
s
t
rest
rest(剩余可用的A数量),
d
e
l
t
a
delta
delta,
p
o
s
pos
pos,
l
e
n
len
len(区间中最小的扩展)
优先队列存储结构体
S
t
a
t
e
State
State
S
t
a
t
e
State
State里存储两棵线段树的根:
i
n
i
t
init
init,
n
o
w
now
now,和一个答案
a
n
s
ans
ans,为
i
n
i
t
init
init时的答案
优先队列的比较以
a
n
s
+
n
o
w
−
>
d
e
l
t
a
ans+now->delta
ans+now−>delta为关键字,选最小值。
进行第一种方法扩展状态时,建立新的
S
t
a
t
e
State
State,从旧的
S
t
a
t
e
State
State的
i
n
i
t
init
init根转移,得到新的线段树,设为新
S
t
a
t
e
State
State的
i
n
i
t
init
init和
n
o
w
now
now,并将新
S
t
a
t
e
State
State的
a
n
s
ans
ans设为旧
S
t
a
t
e
State
State的
a
n
s
+
n
o
w
−
>
a
n
s
ans+now->ans
ans+now−>ans,然后将此状态放入优先队列
进行第二种方法扩展状态时,直接在当前的
n
o
w
now
now线段树上修改,然后再放回优先队列即可。
代码
变量名均与题解相同
#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;
}