决策单调性优化

决策单调性优化

对于最优化DP来说,即决策点具有单调性。

代码实现

分治

P5503 [JSOI2016] 灯塔 为例。

答案 pi=max{hjhi+|ij|}

去除绝对值,分到两种情况中去做,可以先不用考虑上取整,输出时再做即可。

我们先考虑 ji 的情况,对于另外一种,将整个数组翻转过来即可。

fj(i)=hj+ij

hi 提出来,代入 fj

pi=hi+max{fj(i)}

本质上 f 是由同一个函数图像平移出来的。

由于递增容易发现的是每一个函数两两之间只有一个交点。

可以自行画图理解,决策单调性是比较显然的。

pi 取值与 pj 无关,这样的话我们就可以整体二分。

Solve(l,r,L,R) 表示求值区间 [l,r] 决策区间 [L,R]

每一次,找到区间中点 mid 与其决策点 MID,记录答案后分到两边去做。

Solve(l,mid-1,L,MID)Solve(mid+1,r,MID,R)

时间复杂度 O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int maxn=5*1e5;
int n;
int a[maxn+5];
double p[maxn+5];
inline double Calc(int i,int j){
return a[j]-a[i]+sqrt(i-j);
}
void Solve(int l,int r,int L,int R){
if(l>r) return;
int mid=(r-l)/2+l,k;
double mx=0,val;
for(int i=L;i<=R&&i<=mid;i++){
val=Calc(mid,i);
if(val>=mx) mx=val,k=i;
}
p[mid]=max(p[mid],mx);
Solve(l,mid-1,L,k);
Solve(mid+1,r,k,R);
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
Solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(p[i],p[n-i+1]);
Solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(p[i],p[n-i+1]);
for(int i=1;i<=n;i++) printf("%d\n",(int)ceil(p[i]));
return 0;
}

单调队列

P1912 [NOI2009] 诗人小G 为例。

s(i)=j=1i(len[j]+1)

f(i) 表示考虑到第 i 句时的答案。

有状态转移方程:

f(i)=f(j)+(s(i)s(j)1L)P

打表发现其具有决策单调性,证明略过。

由于 f(i) 转移与 f(j) 有关,所以不能整体二分,可以用单调队列。

单调队列存储决策点。

由于决策单调性,我们可以用二分实现函数 Get(x,y) = k 表示决策 x[1,k] 中优于 y

按照从小到大的顺序求 f

首先是加入 0,初始时,其是所有 f 的最优决策点。

接着每一次计算 fi 重复以下步骤。

  • Get(第一个,第二个) < i 时弹出队头,因为它已不优。
  • 更新并记录答案。
  • Get(倒数第二个,最后一个) >= Get(最后一个,i) 弹出队尾,因为其既不优于倒数第二个,也不优于 i
  • 插入 i

应该解释的比较清楚了。

对于本题,需要注意的是,使用 long double 存储答案,因为答案可能会很大,并且当答案很大时并不用输出,不用担心丢失精度。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ldb long double
const int maxn=2e5;
const int maxs=30;
int n,L,P;
char str[maxn+5][maxs+5];
ll s[maxn+5];
ldb f[maxn+5];
int g[maxn+5];
ll q[maxn+5];
int hd,tl;
inline ldb Fpow(ldb x,ll y){
ldb res=1;
if(x<0) x=-x;
for(;y;y>>=1,x*=x)
if(y&1) res*=x;
return res;
}
inline ldb Calc(int i,int j){
return f[j]+Fpow(s[i]-s[j]-L-1,P);
}
inline int Get(int x,int y){
int l=y,r=n,mid,ans=y-1;
while(l<=r){
mid=(l+r)/2;
if(Calc(mid,x)<=Calc(mid,y)) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
inline void Work(){
scanf("%d%d%d",&n,&L,&P);
for(int i=1;i<=n;i++){
scanf("%s",str[i]);
s[i]=s[i-1]+(ll)strlen(str[i])+1;
g[i]=0,f[i]=1e20;
}
hd=1,tl=0;
q[++tl]=0;
f[0]=0;
for(int i=1;i<=n;i++){
while(hd<tl&&Get(q[hd],q[hd+1])<i) hd++;
g[i]=q[hd],f[i]=Calc(i,q[hd]);
while(hd<tl&&Get(q[tl],i)<=Get(q[tl-1],q[tl])) tl--;
q[++tl]=i;
}
if(f[n]>1e18) puts("Too hard to arrange");
else{
printf("%lld",(ll)f[n]);
hd=1,tl=0;
for(int i=n;i>0;i=g[i]) q[++tl]=i;
q[++tl]=0;
for(;tl>0;tl--){
puts("");
for(int i=q[tl]+1;i<=q[tl-1];i++){
if(i<q[tl-1]) printf("%s ",str[i]);
else printf("%s",str[i]);
}
}
}
puts("--------------------");
}
signed main(){
int T;
scanf("%d",&T);
while(T--) Work();
return 0;
}
posted @   DeepSeaSpray  阅读(29)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示