【洛谷P3466】KLO-Building blocks

题目

题目链接:https://www.luogu.com.cn/problem/P3466
给出一个数列,将一个数加一或减一都付出\(1\)的代价。求最小代价使得数列中存在一个长度为\(m\)的子序列每一个数字都相同。

思路

很明显就是枚举每一个长度为\(m\)的区间,然后求中位数搞一搞。
求中位数可以用对顶堆。我们求出中位数后,计算小于中位数的数字之和以及大于中位数的数字之和,就可以计算出答案。
但是这样我们从区间\([i,i+m-1]\)转移到\([i+1,i+m]\)时,需要支持在堆中 删除任意元素,而这是\(STL\)自带的堆没有的功能。而又不想手写堆(
所以我们需要一个能在\(O(\log n)\)的时间复杂度内支持求最值,插入,删除的数据结构。
那么平衡树就可以了。
用平衡树代替堆,维护两棵平衡树即可。
当然这是比较暴力的方法,维护一棵平衡树也是可以的,因为平衡树支持查找中位数(大概吧)。
时间复杂度\(O(n\log m)\)
其实写这道题就是想默一下Treap的板子233

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=800010,Inf=1e9;
int n,m,pos,v,a[N];
ll sum,ans=1e17;

struct Treenode
{
    int lc,rc,size,dat,val,cnt;
};

struct Treap
{
    Treenode tree[N];
    int tot,root;
    ll sum;
    
    int New(int x)
    {
        tree[++tot].val=x;
        tree[tot].size=tree[tot].cnt=1;
        tree[tot].dat=rand();
        return tot;
    }
    
    void pushup(int x)
    {
        tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+tree[x].cnt;
    }
    
    void build()
    {
        root=New(-Inf);
        tree[1].rc=New(Inf);
        pushup(1);
    }
    
    void zig(int &x)
    {
        int y=tree[x].lc;
        tree[x].lc=tree[y].rc; tree[y].rc=x; x=y;
        pushup(tree[x].rc); pushup(x);
    }
    
    void zag(int &x)
    {
        int y=tree[x].rc;
        tree[x].rc=tree[y].lc; tree[y].lc=x; x=y;
        pushup(tree[x].lc); pushup(x);
    }
    
    int Min(int x)  //求平衡树中最小值,最大值下同
    {
        if (!x) return Inf;
        if (tree[x].val==-Inf) return Min(tree[x].rc);
        return min(Min(tree[x].lc),tree[x].val);
    }
    
    int Max(int x)
    {
        if (!x) return -Inf;
        if (tree[x].val==Inf) return Max(tree[x].lc);
        return max(Max(tree[x].rc),tree[x].val);
    }
    
    void insert(int &x,int val)
    {
        if (x==root) sum+=val;
        if (!x) x=New(val);
        else if (tree[x].val==val) tree[x].cnt++;
        else if (val<tree[x].val)
        {
            insert(tree[x].lc,val);
            if (tree[tree[x].lc].dat<tree[x].dat) zig(x);
        }
        else
        {
            insert(tree[x].rc,val);
            if (tree[tree[x].rc].dat<tree[x].dat) zag(x);
        }
        pushup(x);
    }
    
    void del(int &x,int val)
    {
        if (x==root) sum-=val;
        if (tree[x].val==val)
        {
            if (tree[x].cnt>1) tree[x].cnt--;
            else if (tree[x].lc || tree[x].rc)
            {
                if (!tree[x].lc || tree[tree[x].lc].dat<tree[tree[x].rc].dat)
                    zag(x),del(tree[x].lc,val);
                else
                    zig(x),del(tree[x].rc,val);
            }
            else x=0;
        }
        else if (tree[x].val>val) del(tree[x].lc,val);
        else del(tree[x].rc,val);
        pushup(x);
    }
    
    bool find(int x,int val)  //查找平衡树中是否有val这个值
    {
        if (!x) return 0;
        if (tree[x].val==val) return 1;
        if (tree[x].val>val) return find(tree[x].lc,val);
            else return find(tree[x].rc,val);
    }
}Treap1,Treap2;

void update()  //维护两棵。。。对顶平衡树?(雾
{
    while (Treap1.Max(Treap1.root)>Treap2.Min(Treap2.root))
    {
        int x=Treap1.Max(Treap1.root);
        Treap1.del(Treap1.root,x); Treap2.insert(Treap2.root,x);
    }
    while (Treap1.tree[Treap1.root].size-Treap2.tree[Treap2.root].size>=2)
    {
        int x=Treap1.Max(Treap1.root);
        Treap1.del(Treap1.root,x); Treap2.insert(Treap2.root,x);
    }
    while (Treap2.tree[Treap2.root].size>Treap1.tree[Treap1.root].size)
    {
        int x=Treap2.Min(Treap2.root);
        Treap2.del(Treap2.root,x); Treap1.insert(Treap1.root,x);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    srand(n*2333-m*666);  //优秀的重置种子方法
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    Treap1.build(); Treap2.build();
    for (int i=1;i<m;i++)
    {
        Treap1.insert(Treap1.root,a[i]);
        update();
    }
    for (int i=m;i<=n;i++)
    {
        Treap1.insert(Treap1.root,a[i]);
        update();
        ll mid=1LL*Treap1.Max(Treap1.root);
        int cnt1=Treap1.tree[Treap1.root].size,cnt2=Treap2.tree[Treap2.root].size;
        ll cost=mid*cnt1-Treap1.sum+Treap2.sum-mid*cnt2;  //计算代价
        if (cost<ans)
            ans=cost,pos=i-m+1,v=mid;
        if (Treap1.find(Treap1.root,a[i-m+1])) Treap1.del(Treap1.root,a[i-m+1]);
            else Treap2.del(Treap2.root,a[i-m+1]);
        update();
    }
    printf("%lld\n",ans);
    for (int i=1;i<=n;i++)
        if (i>=pos && i<pos+m) printf("%d\n",v);
            else printf("%d\n",a[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/stoorz/p/12237714.html
今日推荐