【2019.1雅礼集训 DAY1 T2】permutation(可持久化线段树)

题意

给出 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=1nAi×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×(ni+1)
为了方便,我们固定 P i P_i Pi n , n − 1 , n − 2 , . . . , 2 , 1 n,n-1,n-2,...,2,1 n,n1,n2,...,2,1,构造 A q i A_{q_i} Aqi使得价值为 ∑ A q i × ( n − i + 1 ) \sum A_{q_i}\times (n-i+1) Aqi×(ni+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} Apos1的项,往后移一位,给 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×(post1)i=t+1pos1A[i]=i=t+1pos1A[pos]A[i]
l e n = p o s − t − 1 len=pos-t-1 len=post1,用 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)+AposAposlen1,又因为 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} AposApos1(第一位为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} Aposlen1以及 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的标记,单点修改即可。

计算新的扩展

有两种新的扩展:

  1. 执行完 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+1Apos1后,标记固定的A,此时 l e n len len全部本来就是1,不需要修改。
  2. 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CaptainHarryChen

随便

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值