[bzoj1367][BOI2004]Sequence 数字序列——思维题+左偏树

题目大意:

给定一个整数序列 a1,a2,,an a 1 , a 2 , ⋅ ⋅ ⋅ , a n ,求出一个递增序列 b1<b2<<bn b 1 < b 2 < · · · < b n ​ ,使得序列 ai a i ​ 和 bi b i ​ 的各项之差的绝对值之和 |a1b1|+|a2b2|++|anbn| | a 1 − b 1 | + | a 2 − b 2 | + · · · + | a n − b n | 最小。

思路:

不得不说这是一道神仙题目,正解的思路是完全想不到啊,然后发现这是一道论文题,对着论文看了好久才勉强看懂。
一般我们会去从数学的角度思考这个题目,但是这里我们要换一个思路。我们先考虑非严格递增的b数列。
1.观察到如果a数列递增,b数列一个一个相等就好了,a数列如果递减,b数列即为a数列的中位数。
2.考虑从1得得到一些解题的思路,把整个a划分为每一段的解都相同的m段,那么每一段都是同一个答案,那么我们看可不可以合并这些序列(因为最后要求答案是不递减的),如果前一段的b小于等于后一段的b可以直接合并,然后不管,但是如果前一段的b大于后一段的b,那么前一段的b要减,后一段的b要加才可以满足题目的限制。
3.现在我们想要怎样才能合并不满足限制的两段,首先先证明一个问题,对于任意一个序列a[1],a[2],…,a[n],如果最优解为 (u,u,…,u),那么在满足u≤u′≤b[1] 或b[n]≤u′≤u的情况下,(b[1],b[2],…,b[n]) 不会比 (u′,u′,…,u′) 更优,证明这个问题之后我们会发现这个序列的解改变了之后还是所有解相同的情况下最优,就可以大大地简化这个问题。
考虑如何证明:u显然是中位数,如果一段不相同的递增的解更优,则得出来u不是中位数,矛盾。因为u是中位数,显然越接近u的u’更优(证毕)。
所以对于前一段的解大于后一段的情况我们要尽量使两解相同的情况下答案最小,几何意义可知又是在中位数得时候最小。
4.现在可以建立一个基本的算法模型,即一个一个地添加数字进去,区间不断合并,直到 b1b2bm b 1 ≤ b 2 ≤ … ≤ b m 为止。考虑如何去维护中位数,发现原本情况下即满足 b1b2bm1 b 1 ≤ b 2 ≤ … ≤ b m − 1 ,如果新添加了一个数使得开始不满足限制,可以发现新的区间的前一半全部包括在了前面每一段的前半截和新添加的数字里面,这样就可以用可并堆维护了。
5.回到最前面的那个问题,题目要求严格递增怎么办?贪心地选一下,即相邻两数差1,我们把a数列每个数减去自己的下标,做到了相邻两个数差1,这样便可以按照之前原来的做法来做了(好巧妙的方法)。
参考资料——黄源河论文

/*=========================
 * Author : ylsoi
 * Problem : Sequence
 * Algorithm : Leftist_Tree
 * Time : 2018.5.12
 * =======================*/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<climits>
#include<stack>
using namespace std;
void File(){
    freopen("[BOI2004]Sequence.in","r",stdin);
    freopen("[BOI2004]Sequence.out","w",stdout);
}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf LLONG_MAX
const int maxn=1e6+10;
int n,a[maxn];
struct Leftist_Tree{
    int ch[maxn][2],fa[maxn],dis[maxn];
    ll va[maxn];
    int Merge(int x,int y){
        if(x==0 || y==0)return x+y;
        if(va[x]<va[y])swap(x,y);
        ch[x][1]=Merge(ch[x][1],y);
        fa[ch[x][1]]=x;
        if(dis[ch[x][0]]<dis[ch[x][1]])
            swap(ch[x][0],ch[x][1]);
        dis[x]=dis[ch[x][1]]+1;
        return x;
    }
    int del(int x){
        fa[ch[x][0]]=fa[ch[x][1]]=0;
        int ret=Merge(ch[x][0],ch[x][1]);
        ch[x][0]=ch[x][1]=0;
        return ret;
    }
}T;
struct node{
    int l,r,top,size;ll u;
}stac[maxn];
int Top;
ll b[maxn];
void work(){    
    stac[++Top]=(node){1,1,1,1,T.va[1]};
    REP(i,2,n){
        int l=stac[Top].r+1;
        stac[++Top]=(node){l,i,i,1,T.va[i]};
        while(Top!=1 && stac[Top].u<stac[Top-1].u){
            --Top;
            stac[Top].top=T.Merge(stac[Top].top,stac[Top+1].top);
            stac[Top].size=stac[Top].size+stac[Top+1].size;
            stac[Top].r=stac[Top+1].r;
            while(stac[Top].size>(stac[Top].r-stac[Top].l+2)/2){
                --stac[Top].size;
                stac[Top].top=T.del(stac[Top].top);
            }
            stac[Top].u=T.va[stac[Top].top];
        }   
    }
    ll ans=0;
    int p=1;
    REP(i,1,n){
        if(i>stac[p].r)++p;
        ans+=abs(T.va[i]-stac[p].u);
        b[i]=stac[p].u+i;
    }
    printf("%lld\n",ans);
    REP(i,1,n)printf("%lld ",b[i]);
}
int main(){
    File();
    scanf("%d",&n);
    REP(i,1,n)scanf("%lld",&T.va[i]);
    REP(i,1,n)T.va[i]-=i;
    work();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值