P3515 [POI2011]Lightning Conductor
给定一个长度为 \(n\) 的序列 \(\{a_n\}\),对于每个 \(i\in [1,n]\) ,求出一个最小的非负整数 \(p\) ,使得 \(\forall j\in[1,n]\),都有 \(a_j\le a_i+p-\sqrt{|i-j|}\)
\(1 \le n \le 5\times 10^{5}\),\(0 \le a_i \le 10^{9}\) 。
第一次遇到的决策单调性优化dp……也是多少带点神奇了,下面浅析一下。
四边形不等式
设 \(w(x,y)\) 是定义在整数集合上的二元函数。若对于定义域上的任意整数 \(a\),\(b\),\(c\),\(d\),其中 \(a≤b≤c≤d\),都有 \(w(a,d)+w(b,c)≥w(a,c)+w(b,d)\) 成立,则称函数 \(w\) 满足四边形不等式。
设 \(w(x,y)\) 是定义在整数集合上的二元函数。若对于定义域上的任意整数 \(a\),\(b\),其中 \(a<b\),都有 \(w(a,b+1)+w(a+1,b)≥w(a,b)+w(a+1,b+1)\) 成立,则函数 \(w\) 满足四边形不等式。
决策单调性
假如对于某一个dp方程,\(dp[i]\) 的最优转移是 \(dp[k]\),那么称 \(k\) 为 \(i\) 的决策点。
而dp方程满足决策单调性指的是,决策点 \(k\) 随着 \(i\) 的增大保持单调不减。
定理:在状态转移方程 \(f[i]=min{f[j]+w(j,i)},j∈[0,i)\) 中,若函数 \(w\) 满足四边形不等式,则 \(f\) 具有决策单调性。
上述结论具体的证明,先挖个坑?
可见,如果一个dp方程满足决策单调性,我们就可以利用这个单调性,在转移的过程中在更小的范围中寻找最优转移,从而避免了 \(O(n)\) 枚举,达到了优化的目的。
利用决策单调性,主要有分治,单调队列两种方法来优化 \(dp\)。
法一:单调队列
每次求得一个新的 \(dp[i]\) 时,思考 \(dp[i]\) 可以作为哪些状态的最优决策。由于决策是单调的,因此总是存在一个 \(pos\),使得 \(pos\) 以前的状态对应的最优决策的位置小于 \(i\),而在 \(pos\) 之后的状态对应的最优决策的位置大于或等于 \(i\)。那么,如何通过单调队列来维护这些决策呢?
用单调队列来维护决策集合 \(p\),存一个三元组 \(k,l,r\) 分别表示 \(l~r\) 之间的所有状态对应的最优决策都是 \(dp[k]\)。
维护过程:
- 令队头存在的三元组为 \((k_0,l_0,r_0)\),假如 \(r_0\) 为 \(i-1\),直接把队首元素弹出。否则把 \(l_0\) 赋值为 \(i\)。
- 取队首元素记录的决策信息直接转移,算出 \(dp[i]\)。
- 将 \(dp[i]\) 加入决策队列中,步骤如下:
- 取出队尾,令其为 \((k_t,l_t,r_t)\)
- 若对于 \(f[l_t]\) 来说,\(i\) 是比 \(k_t\) 更优的决策,即 \(f[i]+w(i,l_t)≤f[k_t]+w(k_t,l_t)\),记 \(pos=l\),删除队尾,返回步骤1。
- 若对于 \(f[r t]\) 来说,\(i\) 是比 \(k_t\) 更优的决策,即 \(f[k_t]+w(j_t,r_t)≤f[i]+w(i,r_t)\),去往步骤5。
- 否则,则在 \([l t,r t]\) 上二分查找出位置 \(pos\),在此之前决策比 \(i\) 优,在此之后决策 \(i\) 更优,将 \([l t,r t]\) 修改为 \([l t,pos−1]\),去往步骤5。
- 把三元组 \((i,pos,n)\) 插入队尾。
——参考于洛谷用户 \(HoshiuZ\)。
复杂度为 \(O(n\log n)\)。
法二:分治
分治比单调队列要好想很多,常受本人青睐,下面介绍一下其步骤。
假设当前处理的区间为 \(L~R\),该区间对应的最优决策所处的区间在 \(l~r\) 之间。
令 \(mid = (L+R) / 2\),然后直接暴力枚举该区间对应的最优决策所处区间的元素,求出 \(dp[mid]\) 对应的最优决策,令其为 \(nr\)。
又由于决策具有单调性,所以 \(mid+1~R\) 这一段区间对应的最优决策所处区间一定在 \(nr~r\) 之间,\(L~mid-1\) 这段区间对应的最优决策所处区间一定在 \(l~nr\) 之间。
对左右两个区间递归操作即可。
前置知识介绍完了,那么这个题如何处理呢?
对题目中的式子进行转化,可得
去掉绝对值,即有:
好像四边形不等式能证明这是满足决策单调性的……留个坑,下周来填。
在知道了dp式子是满足决策单调性的前提下,只需要正反各dp一次,然后求max就行了。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 6e5;
long long a[MAXN + 5],n;
double f1[MAXN + 5],f2[MAXN + 5];
void work1(int L,int R,int l,int r){//范围+决策范围,思路可参考上述介绍的通过分治利用决策单调性。这里是正着求一边
if(L > R)return;
double now = 0;
int mid = (L + R) / 2,nr = 0;
f1[mid] = a[mid];
for(int i = l; i <= min(mid,r); i++){//暴力求dp[mid]的最优决策点
now = a[i] + sqrt(double(mid - i));
if(now > f1[mid])f1[mid] = now,nr = i;
}
if(!nr)nr = mid;
f1[mid] -= a[mid];
work1(L,mid - 1,l,nr);//递归处理子区间
work1(mid + 1,R,nr,r);
}
void work2(int L,int R,int l,int r){//反着求一遍
if(L > R)return;
double now = 0;
int mid = (L + R) / 2,nr = 0;
f2[mid] = a[mid];
for(int i = r; i >= max(l,mid); i--){
now = a[i] + sqrt(double(i - mid));
if(now > f2[mid])f2[mid] = now,nr = i;
}
if(!nr)nr = mid;
f2[mid] -= a[mid];
work2(L,mid - 1,l,nr);
work2(mid + 1,R,nr,r);
}
int main(){
scanf("%lld",&n);
for(int i = 1; i <= n; i++)scanf("%lld",&a[i]);
work1(1,n,1,n);
work2(1,n,1,n);
for(int i = 1; i <= n; i++){
long long ans = 0;
ans = ceil(max(f1[i],f2[i]));
cout << ans << "\n";
}
}