决策单调性优化DP
@
同步发表于CSDN
决策单调性
四边形不等式
定义:
对于二元函数
简记为 跨越
定理1:
若函数满足 都有
则函数满足四边形不等式
证明1:
若,有
若,有
两式相加,得
即
同理可证,
同理可证,,
决策单调性
定义:
考虑转移方程
令
若
定理2:
考虑转移方程,若函数 满足四边形不等式,则 具有决策单调性
证明2:
+ 得:
所以具有决策单调性
形式1
用于优化形如
因为我们只需要找到每一个
法1 分治
考虑求区间
为什么可以呢,因为根据决策单调性,
所以可以分治来求答案。
参考代码:
void solve(int l,int r,int pl,int pr){
// 求pmid=p[mid]
//算出 f[mid]
solve(l,mid-1,p,pmid);
solve(mid+1,r,pmid,pr);
}
法2 二分队列
易证,每一个决策一定是在一个区间内的,例如:
所以可以维护一个单调队列,用于表示每个决策
参考代码:
void solve(){
h=1,t=0;
for(int i=1;i<=n;i++){
insert(i);
if(h<=t&&q[h].r<i)
h++;
else
q[h].l=i;
f[i]=min(f[i],w(q[h].p,i));
}
}
例题 P3515
给定一个长度为
Solution
先变形:
问题变为求
考虑将序列先算一遍,翻转一次后再算一遍,最后求最大值,即变为求
令
此处
定义
则有:
令
原式
对函数
可以发现,它跟四边形不等式符号相反,同样亦可证得
参考代码:
F1:
#include<bits/stdc++.h>
#define int __int128
#define lf double
using namespace std;
const int N=5e5+1,mod=1e9+7;
const lf eps=1e-5;
int n,a[N];
lf sq[N],f[N];
inline lf w(int j,int i){
return 1.0*a[j]+sq[i-j];
}
inline int Ceil(lf x){
return (int)(x+1-eps);
}
void solve(int l,int r,int pl,int pr){
if(l>r)
return ;
int mid=(l+r)>>1,mxp;
lf mx=0;
for(int i=pl;i<=min(mid,pr);i++)
if(w(i,mid)>mx)
mx=w(i,mid),mxp=i;
f[mid]=max(f[mid],mx);
solve(l,mid-1,pl,mxp);
solve(mid+1,r,mxp,pr);
}
signed main(){
n=read();
for(int i=1;i<=n;i++)
a[i]=read(),sq[i]=sqrt((1.0*i));
solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(f[i],f[n-i+1]);
solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(f[i],f[n-i+1]);
for(int i=1;i<=n;i++){
write(Ceil(f[i]-a[i]));
printf("\n");
}
return 0;
}
F2:
#include<bits/stdc++.h>
#define int __int128
#define lf double
using namespace std;
const int N=5e5+1,mod=1e9+7;
const lf eps=1e-5;
int n,a[N],h,t;
lf sq[N],f[N];
struct fy{
int l,r,p;
}q[N];
inline lf w(int j,int i){
return 1.0*a[j]+sq[i-j];
}
inline int Ceil(lf x){
return (int)(x+1-eps);
}
int find_(int t,int x){
int res=q[t].r+1,l=q[t].l,r=q[t].r,p=q[t].p;
while(l<=r){
int mid=(l+r)>>1;
if(w(p,mid)<=w(x,mid))
res=mid,r=mid-1;
else
l=mid+1;
}
return res;
}
void insert(int x){
q[t].l=max(q[t].l,x);
while(h<=t&&w(q[t].p,q[t].l)<=w(x,q[t].l))
t--;
if(h<=t){
int mid=find_(t,x);
if(mid>n)
return ;
q[t].r=mid-1;
q[++t].l=mid,q[t].p=x,q[t].r=n;
}
else{
q[++t].l=x,q[t].p=x,q[t].r=n;
}
}
void solve(){
h=1,t=0;
for(int i=1;i<=n;i++){
insert(i);
if(h<=t&&q[h].r<i)
h++;
else
q[h].l=i;
f[i]=max(f[i],w(q[h].p,i));
}
}
signed main(){
n=read();
for(int i=1;i<=n;i++)
a[i]=read(),sq[i]=sqrt((1.0*i));
solve();
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(f[i],f[n-i+1]);
solve();
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(f[i],f[n-i+1]);
for(int i=1;i<=n;i++){
write(Ceil(f[i]-a[i]));
printf("\n");
}
return 0;
}
形式2
用于优化形如
注意到此种转移方程依赖于前面的值,因此分治法不再适用,所以只能用二分队列,思路跟上面一摸一样。
例题 P3195
P 教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。
P 教授有编号为
为了方便整理,P教授要求:
- 在一个一维容器中的玩具编号是连续的。
- 同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。形式地说,如果将第
件玩具到第 个玩具放到一个容器中,那么容器的长度将为 。
制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为 ,其制作费用为 。其中 是一个常量。P 教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过 。但他希望所有容器的总费用最小。
对于全部的测试点,
Solution
令
补充:图片的
性质
那么把形式
参考Code:
#include<bits/stdc++.h>
#define int __int128
#define lf double
using namespace std;
const int N=5e5+1,mod=1e9+7;
const lf eps=1e-5;
int n,a[N],h,t,L;
int f[N],sum[N];
struct fy{
int l,r,p;
}q[N];
inline int w(int j,int i){
return f[j-1]+(i-j+sum[i]-sum[j-1]-L)*(i-j+sum[i]-sum[j-1]-L);
}
int find_(int t,int x){
int res=q[t].r+1,l=q[t].l,r=q[t].r,p=q[t].p;
while(l<=r){
int mid=(l+r)>>1;
if(w(p,mid)>=w(x,mid))
res=mid,r=mid-1;
else
l=mid+1;
}
return res;
}
void insert(int x){
q[t].l=max(q[t].l,x);
while(h<=t&&w(q[t].p,q[t].l)>=w(x,q[t].l))
t--;
if(h<=t){
int mid=find_(t,x);
if(mid>n)
return ;
q[t].r=mid-1;
q[++t].l=mid,q[t].p=x,q[t].r=n;
}
else{
q[++t].l=x,q[t].p=x,q[t].r=n;
}
}
void solve(){
h=1,t=0;
for(int i=1;i<=n;i++){
insert(i);
if(h<=t&&q[h].r<i)
h++;
else
q[h].l=i;
f[i]=max(f[i],w(q[h].p,i));
}
}
signed main(){
n=read();
L=read();
for(int i=1;i<=n;i++)
a[i]=read(),sum[i]=sum[i-1]+a[i];
solve();
write(f[n]);
return 0;
}
//直接交不能AC哦
//而且此代码与OI-WIKI的略微有些不同
形式3
用于优化形如
注意到其实跟形式
据作者喜好(不是作者太菜),例题中只给出分治做法。
例题 CF833B
将一个长度为
一段区间的价值表示为区间内不同数字的个数。
Solution
考虑决策单调性优化DP
令
其中
证明
手搓几个样例就可以了口糊过去
证明:
令
所以可得:
因此
所以
仅限于分治方法:
接下来就可以 Code了,不过维护
总时间复杂度:
参考Code:
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
using namespace std;
const int N=1e5+1,mod=1e9+7;
int n,k,a[N],f[52][N],cnt[N],ans,l,r;
inline void add(int x){
ans+=(++cnt[x]==1);
}
inline void del(int x){
ans-=(--cnt[x]==0);
}
inline int w(int cl,int cr){
while(l<cl)
del(a[l++]);
while(l>cl)
add(a[--l]);
while(r<cr)
add(a[++r]);
while(r>cr)
del(a[r--]);
return ans;
}
void solve(int l,int r,int pl,int pr,int now){
if(l>r)
return ;
int mid=(l+r)>>1,mxp;
int mx=0;
for(int i=pl;i<=min(mid-1,pr);i++){
int o=f[now-1][i]+w(i+1,mid);
if(o>mx)
mx=o,mxp=i;
}
f[now][mid]=max(mx,f[now-1][mid]);
solve(l,mid-1,pl,mxp,now);
solve(mid+1,r,mxp,pr,now);
}
signed main(){
n=read(),k=read();
for(int i=1;i<=n;i++)
a[i]=read();
l=1,r=0;
ans=0;
for(int i=1;i<=k;i++){
solve(1,n,0,n,i);
}
cout<<f[k][n]<<"\n";
return 0;
}
//注意不开快读会T哦
形式4
用于优化形如
只需要一个简单的操作,就能把这个区间DP的时间复杂度从
for(int k=i;k<j;k++)
改为
for(int k=p[i][j-1];k<=p[i+1][j];k++)
其中
证明可以参考 《算法竞赛》中的 5.10
和洛谷 石子合并 题解区中 Hurricane、 的题解。
例题
Solution
参考代码:
#include<bits/stdc++.h>
//#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
using namespace std;
const int N=5e3+1,mod=1e9+7;
int n,a[N],s[N],f[N][N],p[N][N];
inline int read(){
int x=0;
char s=getchar();
while(s<'0'||s>'9')
s=getchar();
while(s>='0'&&s<='9')
x=x*10+s-'0',s=getchar();
return x;
}
signed main(){
// IOS;
n=read();
for(int i=1;i<=n;i++)
a[i]=a[i+n]=read();
for(int i=1;i<=2*n;i++)
s[i]=s[i-1]+a[i],p[i][i]=i;
for(int len=2;len<=n;len++)
for(int l=1;l<=n*2-len+1;l++){
int r=l+len-1;
int res=1e9;
for(int mid=p[l][r-1];mid<=p[l+1][r];mid++)
if(f[l][mid-1]+f[mid+1][r]+s[r]-s[l-1]-a[mid]<res)
res=f[l][mid-1]+f[mid+1][r]+s[r]-s[l-1]-a[mid],p[l][r]=mid;
f[l][r]=res;
}
int ans=1e9;
cout<<f[1][n];
return 0;
}
后话
参考习题单
参考资料:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App