[BalticOI 2004] Sequence

看黄源河左偏树的论文时找过去的,结果发现了个超级牛的解法 /se ,然后莫名其妙就变成了洛谷和 darkbzoj 的最优解了 /fad

先把 $a_i$ 全部减去一个 $i$ ,然后 $b_i$ 的限制就变成了不降序列了,最后输出加回来即可

然后可以列出一个非常 $\text{naive}$ 的 $\text{DP}$ :

设 $f_{i,j}$ 为转移到第 $i$ 个,当前的 $b_i$ 等于 $j$ 的最小代价

那么转移也很 $\text{naive}$ ,在此直接列出:

$$f_{i,j}=|a_i-j| + \min\limits_{k<=j} f_{i-1,k}$$

将其改写为 $f_i(x)$ ,很明显 $f_i(x)$ 是一个凸函数(证明可用数学归纳法),于是 $f_i(x)$ 的取值如下

设 $L$ 为 $f_{i-1}(x)$ 斜率为 $0$ 的段的左端点

$$f_i(x)=|a_i-x| + \begin{cases} f_{i-1}(x) & x<L \\ f_{i-1}(L) & x \ge L \end{cases}$$

于是我们只需要支持每次加上一个绝对值函数即可,直接分类讨论:

1、 $a_i \ge L$ ,那么显然此时 $L$ 向右移动变为 $a_i$ ,其他不变

2、 $a_i < L$ ,那么 $a_i$ 以左斜率 $-1$  ,以右斜率 $+1$ ,于是显然 $L$ 此时不再是斜率为 $0$ 的左端点了,左端点改变为原本斜率为 $-1$ 的左端点或者是 $a_i$ (看哪个大)

于是发现又是比较套路的堆优化凸函数(自己取的名字),直接用优先队列即可,答案就是每次转移左端点的代价,注意向右转移是没有代价的,所以只有第二种有代价

但是如何输出方案呢?这里给出一种方法,每次先把堆顶(是转移以后的 $L$ 端点)记为答案,然后从 $b_{n-1}$ 开始,往前推,让 $b_i=\min(b_i,b_{i+1})$ ,为什么这是对的?

看最初的 $\text{naive}$ 想法,注意到这是一个 $\text{DP}$ ,所以如果对于当前端点是最优的,那么肯定前面是能够有方案转移成它的,又因为若 $b_i>b_{i+1}$ ,也就是 $L_i>b_{i+1}$ 那么根据我们上面列出的 $f_i(x)$ 的取值,当前情况最优的就是 $f_{i+1}(x)$ 由 $f_i(x)$ 转移过来(第一条)

这里就贴上输出方案的代码

$code$ :

#include<cstdio>
#include<cctype>
#include<queue>

using namespace std;

#define maxn 1001001

template<class T>

inline T read(){
    T r=0,f=0;
    char c;
    while(!isdigit(c=getchar()))f|=(c=='-');
    while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar();
    return f?-r:r;
}

inline int min(int a,int b){
    return a<b?a:b;
}

int n,s[maxn];

long long ans;

priority_queue<int> q;

int main(){
    n=read<int>();
    q.push(s[1]=read<int>()-1);
    for(int i=2;i<=n;i++){
        int v=read<int>()-i;
        int L=q.top();
        if(L>v){
            ans+=L-v;
            q.pop();
            q.push(v);
        }
        q.push(v);
        s[i]=q.top();
    }
    for(int i=n-1;i>=1;i--)s[i]=min(s[i],s[i+1]);
    printf("%lld\n",ans);
    for(int i=1;i<=n;i++)printf("%d ",s[i]+i);
    return 0;
}

 

posted @ 2020-11-28 22:55  一叶知秋‘  阅读(131)  评论(1编辑  收藏  举报