决策单调性优化
决策单调性优化
对于最优化DP来说,即决策点具有单调性。
代码实现
分治
以 P5503 [JSOI2016] 灯塔 为例。
答案 。
去除绝对值,分到两种情况中去做,可以先不用考虑上取整,输出时再做即可。
我们先考虑 的情况,对于另外一种,将整个数组翻转过来即可。
设 。
把 提出来,代入 。
本质上 是由同一个函数图像平移出来的。
由于递增容易发现的是每一个函数两两之间只有一个交点。
可以自行画图理解,决策单调性是比较显然的。
取值与 无关,这样的话我们就可以整体二分。
Solve(l,r,L,R)
表示求值区间 决策区间 。
每一次,找到区间中点 与其决策点 ,记录答案后分到两边去做。
Solve(l,mid-1,L,MID)
与 Solve(mid+1,r,MID,R)
。
时间复杂度
#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 为例。
令 。
设 表示考虑到第 句时的答案。
有状态转移方程:
打表发现其具有决策单调性,证明略过。
由于 转移与 有关,所以不能整体二分,可以用单调队列。
单调队列存储决策点。
由于决策单调性,我们可以用二分实现函数 Get(x,y) = k
表示决策 在 中优于 。
按照从小到大的顺序求 。
首先是加入 ,初始时,其是所有 的最优决策点。
接着每一次计算 重复以下步骤。
- 当
Get(第一个,第二个) < i
时弹出队头,因为它已不优。 - 更新并记录答案。
- 当
Get(倒数第二个,最后一个) >= Get(最后一个,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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现