斜率优化学习笔记
掌握了斜率优化dp,就可以去切一堆紫题了
斜率优化dp原理:利用数学的斜率,通过计算查找得到最优的斜率以及常数项使得答案最优
维护最佳答案选择,我们通常都要维护一个上凸包或者一个下凸包
怎么才是一个优秀的凸包?
只需要斜率单调递增/递减即可
那么我们的这个斜率怎么求?
y与x的比值,分别为常数以及带乘法的类似系数的玩意
具体怎么分析还是需要列式子
实在不行可以把大于号小于号都试试
下面说几种常规的 \(O(n^2)\) 通过斜率优化优化到不同时间复杂度的例子
\(O(n\log^2n)\)
一道泛用性较高的斜率优化dp,但也会比较难写
可得式子
可以化为
此时把只和 \(i\) 有关的都放到一边可以得到式子
\(dp[i]-h[i]*h[i]-pre[i-1]=dp[j]+h[j]*h[j]-2*h[i]*h[j]-pre[j]\)
那么此时y为仅仅与 \(j\) 有关的
而x为\(h[j]\)
此时我们先维护一个凸包,然后进行查询
首先维护凸包,因为每一次的加入的x大小关系不同,我们需要考虑不同的解法
第一种CDQ分治
每次分治在左边建一个凸包,然后给右边查询即可
第二种二进制分组,按照每个二进制位的大小进行凸包的维护,每一次更新都是 \(\log n\) 的,然后我们再进行枚举位置,取最值即可
然后查询时,使用二分查找最优情况即可
CDQ:
#include<bits/stdc++.h>
#define int long long
using namespace std;
/*
dp[i]=min(dp[j]+(h[i]-h[j])^2+pre[i-1]-pre[j])
dp[j]+h[j]*h[j]-pre[j]
-2*h[j]
*/
int n,dp[100005],pre[100005],s[100005],p[100005],h[100005],top;
struct P{
int x,y;
}a[100005],tmp[100005];
bool cmp(P a,P b){
if(a.x!=b.x)return a.x<b.x;
return a.y<b.y;
}
bool slope(int i,int j,int k){
return (tmp[i].y-tmp[j].y)*(tmp[j].x-tmp[k].x)<=(tmp[j].y-tmp[k].y)*(tmp[i].x-tmp[j].x);
}
int calc(int i,int j){
return tmp[j].y+h[i]*h[i]+pre[i-1]-2*h[i]*tmp[j].x;
}
int find(int x){
int l=1,r=top;
while(l<r){
int mid=l+r>>1;
if(calc(x,p[mid])>calc(x,p[mid+1]))l=mid+1;
else r=mid;
}
return p[l];
}
void CDQ(int l,int r){
if(l==r)return;
int mid=l+r>>1;
CDQ(l,mid);
for(int i=l;i<=r;i++)tmp[i]=a[i];
sort(tmp+l,tmp+mid+1,cmp);
top=0;
for(int i=l;i<=mid;i++){
while(top>1&&slope(i,p[top],p[top-1]))top--;
p[++top]=i;
}
for(int i=mid+1;i<=r;i++){
dp[i]=min(dp[i],calc(i,find(i)));
a[i]={h[i],dp[i]+h[i]*h[i]-pre[i]};
}
CDQ(mid+1,r);
}
signed main(){
memset(dp,0x3f,sizeof(dp));
dp[1]=0;
cin>>n;
for(int i=1,x;i<=n;i++)cin>>h[i];
for(int i=1;i<=n;i++)cin>>s[i];
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+s[i];
a[1]={h[1],h[1]*h[1]-pre[1]};
CDQ(1,n);
cout<<dp[n];
return 0;
}
二进制分组:
#include<bits/stdc++.h>
#define int long long
using namespace std;
/*
dp[i]=dp[j]+(h[i]-h[j])^2+pre[i-1]-pre[j]
dp[i]=dp[j]+h[i]*h[i]+h[j]*h[j]-2*h[i]*h[j]+pre[i-1]-pre[j]
*/
struct P{
int x,y;
}a[100005];
int dp[100005],n,h[100005],w[100005],pre[100005],lg[200005];
bool cmp(P a,P b){
if(a.x!=b.x)return a.x<b.x;
return a.y<b.y;
}
bool slope(int i,int j,int k){
return (a[i].y-a[j].y)*(a[j].x-a[k].x)<=(a[j].y-a[k].y)*(a[i].x-a[j].x);
}
int calc(int i,P j){
return j.y+j.x*h[i]+h[i]*h[i]+pre[i-1];
}
int lowbit(int x){
return x&-x;
}
struct CH{
int p[100005],top;
void build(int l,int r){
top=0;
for(int i=l;i<=r;i++)a[i]={-2*h[i],h[i]*h[i]-pre[i]+dp[i]};
sort(a+l,a+r+1,cmp);
for(int i=l;i<=r;i++){
while(top>1&&slope(i,p[top],p[top-1]))top--;
p[++top]=i;
}
}
int query(int k){
int l=1,r=top;
while(l<r){
int mid=l+r>>1;
if(calc(k,a[p[mid]])>=calc(k,a[p[mid+1]]))l=mid+1;
else r=mid;
}
return calc(k,a[p[l]]);
}
}c[25];
signed main(){
for(int i=1;i<=17;i++)lg[(1<<i-1)]=i;
memset(dp,0x3f,sizeof(dp));
dp[1]=0;
cin>>n;
for(int i=1;i<=n;i++)cin>>h[i];
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+w[i];
for(int i=2;i<=n;i++){
int j=lowbit(i-1);
c[lg[j]].build(i-j,i-1);
for(int tmp=i-1;tmp;tmp-=j,j=lowbit(tmp))dp[i]=min(dp[i],c[lg[j]].query(i));
}
cout<<dp[n];
return 0;
}
虽然泛用性强但是又慢还长
但是在其它题目中可能会更简单
比如x是单调递增的,那么可以考虑直接开个队列跑
那么此时时间复杂度为 \(O(n\log n)\)
没有找到板题
还有 \(O(n)\) 的方法
对于最优决策点一定后移的情况,可以直接上单调队列
如羊羊列队
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,a[10005],x[10005],y[10005],dp[10005],p[10005],head,tail;
/*
dp[i][j]=min(dp[k][j-1]+(a[i]-a[k+1])^2)
dp[i][j]=min(dp[k][j-1]+a[k+1]^2-2*a[i]*a[k+1])+a[i]^2
dp[x]+a[x+1]^2-2*a[i]*a[x+1]<=dp[y]+a[y+1]^2-2*a[i]*a[y+1]
y_x-y_y / x_x-x_y <= a[i]
*/
bool slope(int i,int j,int k){
return (y[i]-y[j])*(x[j]-x[k])<=(y[j]-y[k])*(x[i]-x[j]);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=m;i++){
for(int j=0;j<=n;j++)x[j]=2*a[j+1],y[j]=dp[j]+a[j+1]*a[j+1];
head=1,tail=0;
p[++tail]=i-1;
for(int j=i;j<=n;j++){
while(head<tail&&y[p[head+1]]-y[p[head]]<=a[j]*(x[p[head+1]]-x[p[head]]))head++;
dp[j]=y[p[head]]-a[j]*x[p[head]]+a[j]*a[j];
while(head<tail&&slope(j,p[tail],p[tail-1]))tail--;
p[++tail]=j;
}
}
cout<<dp[n];
return 0;
}
那么这斜率优化好像有一点水,能不能上一点点强度?
那是当然有了
首先出场的是 谷仓
虽然只是板子,但是需要拆环枚举
代码:
/*
dp[j]=min(dp[k]+j*(pre[j]-pre[k])-s[j]+s[k])
x<y,y优
dp[x]-i*pre[x]+s[x]>dp[y]-i*pre[y]+s[y]
dp[x]-dp[y]+s[x]-s[y]>(pre[x]-pre[y])*i
*/
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, r[1005], pre[1005], s[1005], ans = 1e16, dp[1005], p[1005], head, tail, x[1005], y[1005];
bool check(int i, int j, int k) { return (y[i] - y[j]) * (x[j] - x[k]) <= (y[j] - y[k]) * (x[i] - x[j]); }
void solve() {
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + r[i];
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + r[i] * i;
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= m; i++) {
head = 1, tail = 0;
p[++tail] = 0;
for (int j = 1; j <= n; j++) y[j] = dp[j] + s[j], x[j] = pre[j];
for (int j = 1; j <= n; j++) {
while (head < tail && y[p[head]] - y[p[head + 1]] > j * (x[p[head]] - x[p[head + 1]])) head++;
dp[j] = y[p[head]] - x[p[head]] * j + j * pre[j] - s[j];
while (head < tail && check(j, p[tail], p[tail - 1])) tail--;
p[++tail] = j;
}
}
ans = min(ans, dp[n]);
}
signed main() {
cin >> n >> m;
for (int i = n; i >= 1; i--) cin >> r[i];
for (int i = 1; i <= n; i++) {
for (int j = 0; j < n; j++) r[j] = r[j + 1];
r[n] = r[0];
solve();
}
cout << ans;
return 0;
}
接下来是 Harbingers
你说的对,但是那时我好像还没有学过树形dp
直接手写一个单调栈,然后记录头部,记得还原即可
代码:
/*
dp[i]=min(dp[j]+(l[i]-l[j])*v[i])+s[i]
x<y
y better
dp[x]-l[x]*v[i]>dp[y]-l[y]*v[i]
Y[x]=dp[x]
X[x]=l[x]
Y[x]-Y[y]>v[i]*(X[x]-X[y])
*/
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, st[100005], top, s[100005], v[100005], dp[100005], x[100005], y[100005], l[100005], b[100005];
struct P {
int x, y;
};
vector<P> e[100005];
bool check(int i, int j, int k) {
return (__int128)(y[i] - y[j]) * (x[j] - x[k]) <= (__int128)(y[j] - y[k]) * (x[i] - x[j]);
}
int calc(int i, int j) { return dp[j] + (l[i] - l[j]) * v[i] + s[i]; }
pair<int, int> push(int x) {
int now = top, tmp, l = 1, r = top;
while (l < r) {
int mid = l + r >> 1;
if (check(x, st[mid + 1], st[mid]))
r = mid;
else
l = mid + 1;
}
top = l;
tmp = st[++top], st[top] = x;
return { now, tmp };
}
void reset(int x, int y) {
st[top] = y;
top = x;
}
int find(int v) {
int l = 1, r = top;
while (l < r) {
int mid = l + r >> 1;
if (calc(v, st[mid]) >= calc(v, st[mid + 1]))
l = mid + 1;
else
r = mid;
}
return st[l];
}
void dfs(int p, int fa, int sum) {
l[p] = sum;
dp[p] = calc(p, find(p));
x[p] = l[p];
y[p] = dp[p];
pair<int, int> tmp2 = push(p);
for (P i : e[p])
if (i.x != fa)
dfs(i.x, p, sum + i.y);
reset(tmp2.first, tmp2.second);
}
signed main() {
cin >> n;
for (int i = 1, a, b, c; i < n; i++) {
cin >> a >> b >> c;
e[a].push_back({ b, c });
e[b].push_back({ a, c });
}
for (int i = 2; i <= n; i++) cin >> s[i] >> v[i];
dfs(1, 0, 0);
for (int i = 2; i <= n; i++) cout << dp[i] << ' ';
return 0;
}
市场监控来了
太好啦是斜率优化,我们有救啦
完啦是分块,我们又没救啦
到底是哪个人才想到在分块里面维护凸包来斜优的
调代码时长两年半
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,B,id[100005],L[405],R[405];
struct P{
int x,y,id;
bool friend operator<(P a,P b){
if(a.x!=b.x)return a.x<b.x;
return a.y<b.y;
}
}a[100005];
bool slope(P i,P j,P k){
return (__int128)(i.y-j.y)*(j.x-k.x)>=(__int128)(j.y-k.y)*(i.x-j.x);
}
struct CV{
int top,len;
P b[405],p[405];
void build(){
top=0;
for(int i=1;i<=len;i++){
while(top>1&&slope(b[i],p[top],p[top-1]))top--;
p[++top]=b[i];
}
}
void change(int w,P v){
bool f=0;
int q;
for(int i=1;i<=len;i++){
if(b[i].id==w){
f=1;
q=i;
b[i]=v;
break;
}
}
if(!f){
b[++len]=v;
q=len;
}
while(q<len&&!(b[q]<b[q+1])){
swap(b[q],b[q+1]);
q++;
}
while(q>1&&b[q]<b[q-1]){
swap(b[q],b[q-1]);
q--;
}
build();
}
int query(int x){
int l=1,r=top;
if(!top)return -1e16;
while(l<r){
int mid=l+r>>1;
if(p[mid].y-p[mid+1].y<(p[mid+1].x-p[mid].x)*x)l=mid+1;
else r=mid;
}
return p[l].x*x+p[l].y;
}
}st[405];
void init(){
B=sqrt(n)+1;
for(int i=1;i<=n;i++){
int x=(i-1)/B+1;
id[i]=x;
if(!L[x])L[x]=i;
R[x]=i;
}
}
int query(int k,int l,int r){
int ans=-1e16;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)if(a[i].id)ans=max(ans,a[i].x*k+a[i].y);
return ans;
}
for(int i=l;i<=R[id[l]];i++)if(a[i].id)ans=max(ans,a[i].x*k+a[i].y);
for(int i=id[l]+1;i<id[r];i++)ans=max(ans,st[i].query(k));
for(int i=L[id[r]];i<=r;i++)if(a[i].id)ans=max(ans,a[i].x*k+a[i].y);
return ans;
}
signed main(){
cin>>n>>m;
init();
int op,t,k,s,z,l,r;
while(m--){
cin>>op;
if(op==1){
cin>>t>>k>>z>>s;
a[k]={z,s-z*t,k};
st[id[k]].change(k,a[k]);
}
else {
cin>>t>>l>>r;
if(l>r)swap(l,r);
int tmp=query(t,l,r);
if(tmp==-1e16)puts("nema");
else cout<<tmp<<endl;
}
}
return 0;
}
完结撒花

浙公网安备 33010602011771号