斜率优化
原谅我之前做斜率优化题的时候都是在瞎bb,这才是靠谱的
比如我们有个这样的式子,要求最大化 Y = f [ i ] = A ( i ) + B ( j ) + C ( i ) D ( j ) Y=f[i]=A(i)+B(j)+C(i)D(j) Y=f[i]=A(i)+B(j)+C(i)D(j)
我们可以先忽略 A ( i ) A(i) A(i)
则 Y = C ( i ) D ( j ) + B ( j ) Y=C(i)D(j)+B(j) Y=C(i)D(j)+B(j)
相当于很多直线,斜率为 D ( j ) D(j) D(j),截距为 B ( j ) B(j) B(j),求 x = C ( i ) x=C(i) x=C(i)时的最大值,这是一个半平面交的问题。由于半平面交和凸包是对偶问题,所以我们考虑转化为凸包问题。
− C ( i ) D ( j ) + Y = B ( j ) -C(i)D(j)+Y=B(j) −C(i)D(j)+Y=B(j)
相当于有很多点 ( D ( j ) , B ( j ) ) (D(j),B(j)) (D(j),B(j)),给定斜率为 − C ( i ) -C(i) −C(i),则要求最大化截距。显然最优的点必定是在上凸壳上,而根据一些初中数学知识,我们可以知道当这个斜率被某个点两边斜率夹在中间时为最优的点。
如果不单调的话在凸壳上二分即可。而如果 − C ( i ) -C(i) −C(i)是单调的,我们可以单调队列/单调栈。
最小化同理。
Problem
Solution
这题有个性质,对于选定的区间 [ L , R ] [L,R] [L,R]选定的 s s s必定是等于左右端的贝壳大小的,因为如果不是的话,显然多余的长度不会做任何贡献,我们把端点缩减是更好的。
f [ i ] = max a [ j ] = a [ i ] ( f [ j − 1 ] + a [ j ] ∗ ( c i − c j + 1 ) 2 ) f[i]=\max_{a[j]=a[i]}(f[j-1]+a[j]*(c_i-c_j+1)^2) f[i]=a[j]=a[i]max(f[j−1]+a[j]∗(ci−cj+1)2)
f [ i ] = max a [ j ] = a [ i ] ( f [ j − 1 ] + a i c i 2 + 2 a i c i + a i + a j c j 2 − 2 a j c j − 2 a i c i c j ) f[i]=\max_{a[j]=a[i]}(f[j-1]+a_ic_i^2+2a_ic_i+a_i+a_jc_j^2-2a_jc_j-2a_ic_ic_j) f[i]=a[j]=a[i]max(f[j−1]+aici2+2aici+ai+ajcj2−2ajcj−2aicicj)
我们把各式一一对应一下,注意 a i = a j a_i=a_j ai=aj,所以可以变化一下:
A ( i ) = a i c i 2 + 2 a i c i + a i A(i)=a_ic_i^2+2a_ic_i+a_i A(i)=aici2+2aici+ai
B ( j ) = a j c j 2 − 2 a j c j + f [ j − 1 ] B(j)=a_jc_j^2-2a_jc_j+f[j-1] B(j)=ajcj2−2ajcj+f[j−1]
C ( i ) = − 2 a i c i C(i)=-2a_ic_i C(i)=−2aici
D ( j ) = c j D(j)=c_j D(j)=cj
然后只需要对于每个 a i a_i ai维护一个凸包,则斜率是单调递增的,用单调栈搞一搞即可。
时间复杂度 O ( n ) O(n) O(n)。
Code
#include <cstdio>
#include <vector>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=100010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,x,a[maxn],c[maxn],vis[10010];
ll now,f[maxn];
vector<int> stk[10010];
ll B(int i){return (ll)a[i]*c[i]*c[i]-2ll*a[i]*c[i]+f[i-1];}
double slope(int i,int j){return (B(j)-B(i))*1.0/(c[j]-c[i]);}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
read(n);
for(rg int i=1;i<=n;i++)
{
read(a[i]);
c[i]=++vis[a[i]];
}
for(rg int i=1;i<=n;i++)
{
int top=stk[a[i]].size()-1;
now=2ll*a[i]*c[i];
while(top>0&&slope(stk[a[i]][top-1],stk[a[i]][top])<slope(stk[a[i]][top],i))
{
stk[a[i]].pop_back();
--top;
}
++top;
stk[a[i]].push_back(i);
while(top>0&&slope(stk[a[i]][top-1],stk[a[i]][top])<now)
{
stk[a[i]].pop_back();
--top;
}
x=stk[a[i]][top];
f[i]=f[x-1]+(ll)a[i]*(c[i]-c[x]+1)*(c[i]-c[x]+1);
}
printf("%lld\n",f[n]);
return 0;
}
决策单调性优化
适用一些形如这样的式子
f
[
i
]
=
min
(
f
[
j
]
+
w
(
j
+
1
,
i
)
)
f[i]=\min (f[j]+w(j+1,i))
f[i]=min(f[j]+w(j+1,i)),且费用的计算代价较小。
如果满足
w
(
i
,
j
+
1
)
−
w
(
i
,
j
)
≥
w
(
i
+
1
,
j
+
1
)
−
w
(
i
+
1
,
j
)
w(i,j+1)-w(i,j)\geq w(i+1,j+1)-w(i+1,j)
w(i,j+1)−w(i,j)≥w(i+1,j+1)−w(i+1,j),那么就具有决策单调性,即
∀
i
<
j
b
e
s
t
(
i
)
≤
b
e
s
t
(
j
)
\forall_{i<j}best(i)\leq best(j)
∀i<jbest(i)≤best(j)
这个不等式的意义可以认为是转移位置越大,费用的增加量是不升的。所以当决策点为 j j j时,对于所有的 k < j k<j k<j,它的增加的费用是越来越大的,肯定不会比j更优。
如果最优决策点是单调的,我们可以存下某些点以它为最优决策点的区间,不妨称为有效区间,很显然这可以覆盖整个区间。然后我们拿队列把它全都存起来即可。转移i时,我们就可以检测一下队首是否需要弹出。
然后如果从i转移第n项更优,那么就可以将这个决策点入队,不妨设队尾元素的有效区间为 [ L , n ] [L,n] [L,n],有两种情况
- 如果从i转移L更优,那么直接弹出队尾元素即可,继续检测
- 否则,在 [ L , n ] [L,n] [L,n]区间内二分出分割点,然后把队尾的有效区间拆开,入队
注意如果费用计算复杂度高,可以考虑换用分治的形式来优化。
Problem
Solution
对于i,我们可以把j分为 j ≤ i j\leq i j≤i和 j ≥ i j\geq i j≥i两种情况进行dp。然后dp两次取最大即可。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
Code
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <queue>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=500010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct data{
int l,r,id;
data(const int _l,const int _r,const int _id){l=_l;r=_r;id=_id;}
};
int n,a[maxn];
double sqr[maxn],f[maxn],g[maxn];
deque<data> q;
double calc(int i,int j){return a[j]-a[i]+sqr[i-j];}
void dp(double *f)
{
while(!q.empty()) q.pop_front();
int l,r,mid,tmp;
q.push_back(data(1,n,1));
for(rg int i=2;i<=n;i++)
{
++q.front().l;
while(q.front().l>q.front().r) q.pop_front();
if(q.empty()||(calc(n,q.back().id)<calc(n,i)))
{
while(!q.empty()&&calc(q.back().l,q.back().id)<=calc(q.back().l,i))
q.pop_back();
if(q.empty()) q.push_back(data(i,n,i));
else
{
l=q.back().l;r=q.back().r;
while(l<=r)
{
mid=(l+r)>>1;
if(calc(mid,q.back().id)<calc(mid,i)) tmp=mid,r=mid-1;
else l=mid+1;
}
q.back().r=tmp-1;
q.push_back(data(tmp,n,i));
}
}
f[i]=calc(i,q.front().id);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
read(n);
for(rg int i=1;i<=n;i++){read(a[i]);sqr[i]=sqrt((double)i);}
dp(f);
reverse(a+1,a+n+1);
dp(g);
for(rg int i=1;i<=n;i++)
printf("%lld\n",max((ll)ceil(f[i]),(ll)ceil(g[n-i+1])));
return 0;
}