题解-BOI 2004 Sequence

Problem

bzoj & Luogu

题目大意:

给定序列\(\{a_i\}\),求一个严格递增序列\(\{b_i\}\),使得\(\sum \bigl |a_i-b_i\bigr|\)最小

Thought

正序:直接对应

逆序:取中位数(证明:“医院设置”)

最优解一定是分段

每一段台阶式上升

每一段选取中位数

沙漏型左偏树 合并区间 选取中位数

upd:貌似不需要沙漏型?

Solution

前置技能:小学奥数、可并堆

和上面类似,先不考虑严格上升,即先考虑非严格上升

序列一定是要分成若干段,每一段的\(b\)值相等,且后一段比前一段大,像台阶一样(如下图,是一个\(b(x)\)的伪函数)

先令\(\forall i\in[1,n],a_i=b_i\),这样的答案为零,但却不合法,接下来考虑如何用最小代价使答案合法,考虑对于相邻两段数:

设当前前一段取最优值时的\(b\)统一为\(b_1\),后一段统一为\(b_2\),变换之后两者的统一\(b\)值分别变为\(b_1^{‘},b_2^{‘}\)

如果\(b_1\leq b_2\),则对于这两段来说是合法的,无需操作;

如果\(b_1>b_2\),则表示因为要求\(b_1\leq b_2\),而现在是\(b_1>b_2\),要求\(b_1^{‘}\leq b_2^{‘}\),考虑到两段的\(b\)变化得越少越好,即\(\bigl | b_1-b_1^{‘}\bigr |,\bigl | b_1-b_1^{‘}\bigr |\)取最小,则变换之后\(b_1^{‘}=b_2^{‘}\),我们再考虑\(b_1^{‘}(b_2^{‘})\)的取值,应为这两段数合在一起的中位数,证明见下方“附”,找中位数可以用线段树解决,也可以用堆解决(堆解法见TJOI2010中位数),考虑到两段需要合并,线段树需要线段树合并,而堆只需要可并堆即可

如何把相邻两段的处理扩展到整个序列呢,鉴于整个\(b\)序列是递增的,可以用单调栈实现,栈中的比较方式就是上述对于相邻两段的处理

现在解除一开始自己设置的限制,将\(a_i\)设为\(a_i-i\)即可将非严格上升序列的做法转移到严格上升序列的做法

附:证明:其实就是小学奥数题 对于一段数\(\{c_i\}\)选取\(x\)使得\(\sum \bigl |x-c_i \bigr |\),最小的\(x\)值的一个取值为\(\{c_i\}\)序列的中位数:

反证法:设原序列有\(n\)个元素,则比\(x\)大/小的数有\(\frac n2\)个,若\(x\)变小或变大,则若越过序列中另一个值时,比\(x\)大/小的数有\(\frac n2±1\)个,统计答案时只会增加\(2\)或不变

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rg register

struct ios {
    inline char read(){
        static const int IN_LEN=1<<18|1;
        static char buf[IN_LEN],*s,*t;
        return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
    }

    template <typename _Tp> inline ios & operator >> (_Tp&x){
        static char c11,boo;
        for(c11=read(),boo=0;!isdigit(c11);c11=read()){
            if(c11==-1)return *this;
            boo|=c11==‘-‘;
        }
        for(x=0;isdigit(c11);c11=read())x=x*10+(c11^‘0‘);
        boo&&(x=-x);
        return *this;
    }
} io;

const int N=1001000;
struct Leftist_Tree{int l,r,dis,val;}t[N];
struct node{
    int l,r,rt,sz,val;
    node(){}
    node(const int&L,const int&id){l=L,r=rt=id,sz=1,val=t[id].val;}
}h[N];
int n,top;

inline int merge(int u,int v){
    if(!u||!v)return u|v;
    if(t[u].val<t[v].val||(t[u].val==t[v].val&&u>v))swap(u,v);
    int&l=t[u].l,&r=t[u].r;
    r=merge(r,v);
    if(t[l].dis<t[r].dis)swap(l,r);
    t[u].dis=t[r].dis+1;
    return u;
}

inline int del(int u){return merge(t[u].l,t[u].r);}

void work(){
    io>>n;
    for(rg int i=1;i<=n;++i)io>>t[i].val,t[i].val-=i;
    h[top=1]=node(1,1);
    for(rg int i=2;i<=n;++i){
        int l=h[top].r+1;
        h[++top]=node(l,i);
        while(top^1&&h[top-1].val>h[top].val){
            --top;
            h[top].rt=merge(h[top].rt,h[top+1].rt);
            h[top].r=h[top+1].r;
            h[top].sz+=h[top+1].sz;
            while(h[top].sz>((h[top].r-h[top].l+2)>>1)){
                --h[top].sz;
                h[top].rt=del(h[top].rt);
            }h[top].val=t[h[top].rt].val;
        }
    }return ;
}

void Print(){
    ll Ans=0;
    for(rg int i=1,p=1;i<=n;++i){
        if(i>h[p].r)++p;
        Ans+=abs(h[p].val-t[i].val);
    }printf("%lld\n",Ans);
    for(rg int i=1,p=1;i<=n;++i){
        if(i>h[p].r)++p;
        printf("%d ",h[p].val+i);
    }putchar(‘\n‘);
    return ;
}

int main(){
    work();
    Print();
    return 0;
}

原文地址:https://www.cnblogs.com/penth/p/9239977.html

时间: 2024-08-12 04:16:01

题解-BOI 2004 Sequence的相关文章

PAT甲题题解-1051. Pop Sequence (25)-堆栈

将1~n压入最多为m元素的栈 给出k个出栈序列,问你是否能够实现. 能输出YES 否则NO 模拟一遍即可,水题. #include <iostream> #include <cstdio> #include <string.h> #include <algorithm> using namespace std; const int maxn=1005; int m,n,k; int seq[maxn]; int stacks[maxn]; int rear=

COJN 0585 800604鸡蛋的硬度

800604鸡蛋的硬度 难度级别:B: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 最近XX公司举办了一个奇怪的比赛:鸡蛋硬度之王争霸赛.参赛者是来自世 界各地的母鸡,比赛的内容是看谁下的蛋最硬,更奇怪的是XX公司并不使用什么精密仪器来测量蛋的硬度,他们采用了一种最老土的办法--从高度扔鸡蛋--来 测试鸡蛋的硬度,如果一次母鸡下的蛋从高楼的第a层摔下来没摔破,但是从a+1层摔下来时摔破了,那么就说这只母鸡的鸡蛋的硬度是a.你当然可以找出

SPOJ 687. Repeats(后缀数组求最长重复子串)

题目大意:给你一个串让你求出重复次数最多的连续重复子串的重复次数. 解题思路:论文上给出的解答是: 这还没完,因为经过这两个点的情况还不完备,应还可以假设起点在 [ i*j-i+1, i*j-d],其中 d = i-L/i (d = i-L%i)其意义为根据已知的匹配长度,可以将起点往前移动的范围,太靠后将不能够构造出比之前更好的解.如果要求出某个最多的连续重复子串的最小字典序子需要枚举所有起点,但如果只是要的到最多的重复次数或者任意最多的连续重复子串,那么只需要枚举i*j-d处的起点即可,因为

HDU 5014 Number Sequence(2014 ACM/ICPC Asia Regional Xi&#39;an Online) 题解

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5014 Number Sequence Problem Description There is a special number sequence which has n+1 integers. For each number in sequence, we have two rules: ● ai ∈ [0,n] ● ai ≠ aj( i ≠ j ) For sequence a and sequ

POJ 1019 Number Sequence 题解

这又是一道看似简单,实际挺困难的题目. 本来想做道基础题消遣一下的,没想到反被消遣了-_-|||. 看个人的基础吧,对于数学好的会简单点,但是由于情况太多,需要都考虑全,故此难度应该在4星以上了. 我这里使用的方法就是直接打表,然后直接模拟,利用打表去掉一大段数据,剩下数据量十分小了,故此可以直接模拟. 打表是为了计算前面的周期数,把周期数直接去掉. 主要难点是后面10位数以上的数有2位, 3位,4位等情况要考虑.- 下面使用getNewNums一个函数解决了,想通了,就几行代码,还不用难理解的

TopCoder SRM 625 Incrementing Sequence 题解

本题就是给出一个数k和一个数组,包括N个元素,通过每次增加数组中的一个数的操作,最后需要得到1 - N的一个序列,不用排序. 可以从暴力法入手,然后优化. 这里利用hash表进行优化,最终得到时间效率是O(n*n)的算法,而且常数项应该很低,速度还挺快的. 思路: 1 如果数组A[i]在1 -N 范围内,就利用bool B[]记录,这个数已经找到了: 2 如果A[i]的值之前已经找到了,那么就增加k操作,得到新的值A[i]+k,看这个值是否找到了,如果没找到,就使用B记录,如果之前已经找到了,那

HDU 1711 Number Sequence KMP题解

KMP查找整数数列,不是查找字符串. 原理是一样的,不过把字符串转换为数列,其他基本上是一样的. #include <stdio.h> #include <string.h> const int MAX_N = 1000001; const int MAX_M = 10001; int strN[MAX_N], strM[MAX_M], next[MAX_M], N, M; void getNext() { memset(next, 0, sizeof(int) * M); for

NOIP提高组2004 合并果子题解

NOIP提高组2004 合并果子题解 描述:在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和.可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了.多多在合并果子时总共消耗的体力等于每次合并所耗体力之和. 因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力.假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出

【CF486E】LIS of Sequence题解

[CF486E]LIS of Sequence题解 题目链接 题意: 给你一个长度为n的序列a1,a2,...,an,你需要把这n个元素分成三类:1,2,3: 1:所有的最长上升子序列都不包含这个元素 2:有但非所有的最长上升子序列包含这个元素 3:所有的最长上升子序列都包含这个元素 输入格式: 第一行包含一个正整数n,表示序列的长度.第二行包含n个正整数a1,a2,...,an,表示序列中的元素. 输出格式: 一行,包含一个长度为n的.由1,2,3三种数字组成的字符串,第i个数字表示ai所属类