看黄源河左偏树的论文时找过去的,结果发现了个超级牛的解法 /se ,然后莫名其妙就变成了洛谷和 darkbzoj 的最优解了 /fad
先把 $a_i$ 全部减去一个 $i$ ,然后 $b_i$ 的限制就变成了不降序列了,最后输出加回来即可
然后可以列出一个非常 $ ext{naive}$ 的 $ ext{DP}$ :
设 $f_{i,j}$ 为转移到第 $i$ 个,当前的 $b_i$ 等于 $j$ 的最小代价
那么转移也很 $ ext{naive}$ ,在此直接列出:
$f_{i,j}=|a_i-j| + $minlimits_{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| + egin{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})$ ,为什么这是对的?
看最初的 $ ext{naive}$ 想法,注意到这是一个 $ ext{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 ",ans); for(int i=1;i<=n;i++)printf("%d ",s[i]+i); return 0; }