决策单调性是对于一些dp式子,比如说
a
n
s
[
i
]
=
max
(
a
[
j
]
+
i
−
j
)
(
j
<
i
)
ans[i] = \max(a[j] + \sqrt{i-j}) (j < i)
ans[i]=max(a[j]+i−j)(j<i)
如果在一个
i
i
i满足
a
[
j
]
+
i
−
j
<
a
[
k
]
+
i
−
k
a[j] + \sqrt{i-j} < a[k] + \sqrt{i - k}
a[j]+i−j<a[k]+i−k且
j
<
k
j<k
j<k,那么可以发现在
i
i
i变大的时候
j
j
j也一定会比k劣,没有优于
k
k
k的可能。
但是对于这个决策单调性的利用方式却很多。
1.四边形不等式优化二维
d
p
dp
dp,利用
d
p
[
i
]
[
j
−
1
]
dp[i][j-1]
dp[i][j−1]的决策点
<
=
d
p
[
i
]
[
j
]
<=dp[i][j]
<=dp[i][j]的
<
=
d
p
[
i
+
1
]
[
j
]
<=dp[i+1][j]
<=dp[i+1][j]的。在求解
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的时候就可以缩小决策点范围,做到
O
(
n
2
∗
d
)
O(n^2 * d)
O(n2∗d),其中转移一次的复杂度为
O
(
d
)
O(d)
O(d)
2.分治法优化形如
d
p
[
i
]
[
0
n
]
dp[i][0~n]
dp[i][0 n]转移至
d
p
[
i
+
1
]
[
0
n
]
dp[i+1][0~n]
dp[i+1][0 n]
这个就是用分治,
s
o
l
v
e
(
l
,
r
,
q
l
,
q
r
)
solve(l,r,ql,qr)
solve(l,r,ql,qr)表示对于
d
p
[
i
+
1
]
[
q
l
到
q
r
]
dp[i+1][ql到qr]
dp[i+1][ql到qr]的决策点已锁定在
d
p
[
i
]
[
l
,
r
]
dp[i][l,r]
dp[i][l,r]间,继续往下分治,每次(扫
l
l
l到
r
r
r一遍)找到
m
i
d
=
q
l
+
q
r
2
mid = \frac {ql + qr} 2
mid=2ql+qr的决策点
k
k
k,然后执行
s
o
l
v
e
(
l
,
k
,
q
l
,
m
i
d
−
1
)
solve(l,k,ql,mid-1)
solve(l,k,ql,mid−1),
s
o
l
v
e
(
k
,
r
,
m
i
d
+
1
,
q
r
)
solve(k,r,mid+1,qr)
solve(k,r,mid+1,qr).
和后面那个比起来编程复杂度基本为
0
0
0.。。。
3.二分+单调队列维护
既然点的决策点满足单调性,那么我们可以反过来,决策点能够作为最优决策点的点一定是连续的一段区间。
比如说我们用s[i]表示i的最优决策点。
可能它是这样的:
s : 1 2 3 4 5 6 7 8 9 10…
= 1 1 2 2 2 3 3 5 6 7 8 …
所以用单调队列维护这些决策点和他们的影响区间。。。。。。
加入一个新点的时候先看能不能将队尾直接挤出,不能再用二分求出各自的影响区间。
细节超多,常数较上一个小了五分之一,也比上一个实用。
例题:[POI2011]Lightning Conductor
分治法
#include<bits/stdc++.h>
#define maxn 500005
using namespace std;
int n,a[maxn],ans[2][maxn];
inline double calc(int u,int v){ return sqrt(abs(v-u))+a[u]; }
void solve(int l,int r,int ql,int qr,int ans[maxn])
{
if(l>r || ql>qr) return;
int Minloc = l,mid=(ql+qr)>>1;double tmp = calc(l,mid) ,res = 0;
for(int i=l+1;i<=r && i<=mid;i++) if(tmp<=(res=calc(i,mid))) tmp = res , Minloc = i;
ans[mid] = max(ans[mid] , int(ceil(tmp)) - a[mid]);
solve(l,Minloc,ql,mid-1,ans),solve(Minloc,r,mid+1,qr,ans);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
solve(1,n,1,n,ans[0]);
for(int i=1;i<n-i+1;i++) swap(a[i],a[n-i+1]);
solve(1,n,1,n,ans[1]);
for(int i=1;i<=n;i++) printf("%d\n",max(ans[0][i],ans[1][n-i+1]));
}
单调队列法:
#include<bits/stdc++.h>
#define maxn 500005
using namespace std;
char cb[1<<15],*cs=cb,*ct=cb;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
inline void read(int &res){ char ch;for(;!isdigit(ch=getc()););for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0'); }
int n,a[maxn],ans[2][maxn];
inline double calc(int u,int v){ return a[u]+sqrt(abs(u-v)); }
void solve(int ans[maxn])
{
deque<int>q;
static int l[maxn],r[maxn];
for(int i=1;i<=n;i++)
{
for(;q.size()>1 && calc(q.back(),l[q.back()]) <= calc(i,l[q.back()]);q.pop_back());
if(!q.empty())
{
int L = max(l[q.back()],i) , R = r[q.back()]+1;
for(int mid;L<R;)
{
mid = (L+R)>>1;
if(calc(i,mid) >= calc(q.back(),mid)) R = mid;
else L = mid+1;
}
l[i] = L , r[i] = n;
r[q.back()] = L-1;
}
else l[i] = i , r[i] = n;
q.push_back(i);
for(;!q.empty() && r[q.front()] < i;q.pop_front());
ans[i] = ceil(calc(q.front(),i)) - a[i];
}
}
int main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
solve(ans[0]);
for(int i=1;i<n-i+1;i++) swap(a[i],a[n-i+1]);
solve(ans[1]);
for(int i=1;i<=n;i++) printf("%d\n",max(ans[0][i],ans[1][n-i+1]));
}