html z-dext优先级顺序,$ext{1D/1D}$ 动态规划的三种优化

本文探讨了一维动态规划中的四边形不等式及其与决策单调性的紧密联系,通过示例证明了如何利用决策单调性简化DP方程。重点介绍了两种优化方法,包括二分栈从左到右扫描和决策单调性下的快速计算。文章以诗人小G的题目为例,展示了如何通过这些技巧提高算法效率。
摘要由CSDN通过智能技术生成

神必博主的沙雕前言

参考文献:

概念明晰

所谓 (ext{1D/1D}) 动态规划, 指的是状态数和单状态决策数都是 (O(n)) 的动态规划方程, 暴力求解的时间复杂度为 (O(n^2))。

四边形不等式

(f[i] = min/max_{j in [1,i-1]} { f[j] + w(j,i) }) 形式及四边形不等式的定义

下面只考虑取 (min)。

决策单调性是指对于 (a

[f[b] + w(b, c) leq f[a] + w(a, c) Rightarrow f[b] + w(b, d) leq f[a] + w(a, d) ag{1}

]

那么显然对于 (w) 函数来说, 如果满足这个等式:(-w(b,c)+w(b,d) le -w(a,c)+w(a,d)), 就可以使得 ((1)) 成立。

将此等式转化一下, 就得到了 四边形不等式:

[w(a,c)+w(b,d) le w(a,d)+w(b,c) ag{$a

]

对于 (max) 的情况也差不多, 只是等号的方向变了一下。

可以看出四边形不等式与决策单调性有着很亲密的关系。

四边形不等式的判定与性质

还是以 (min) 来说明。

有一个与四边形不等式等价的式子, 若函数 (w) 对于任意 (a

不会证。

3道练证明的例题

诗人小G

很显然的 (DP) 方程:

[f[i] = min_{j=0}^{i-1}{f[j]+w(j,i)}

]

其中, (w(j,i) = Bigg| [i-(j+1)+1-1] + sum_{k=j+1}^i a[k] -L Bigg|^P), 若记 (s[i] = sum_{k=1}^i a[k]), 则 (w(j,i) = Bigg|i-j-1+s[i]-s[j]-L Bigg|^P) 。

如果 (w) 满足四边形不等式, 那么这个 (DP) 方程就满足决策单调性。

只需证明 (w(i,j) + w(i+1,j+1) le w(i+1,j) + w(i,j+1)) 。

展开, 得到

[Bigg|i-j-1+s[i]-s[j]-L Bigg|^P + Bigg|i-j-1+s[i+1]-s[j+1]-L Bigg|^P le

]

[Bigg|i-j+s[i+1]-s[j]-L Bigg|^P + Bigg|i-j-2+s[i]-s[j+1]-L Bigg|^P

]

设 (u = i-j-2+s[i]-s[j+1]-L), (v = i-j-1+s[i]-s[j]-L), 则原式变成

[|v|^P - ig|v+1+a[i+1]ig|^P le |u|^P- ig|u+1+a[i+1]ig|^P

]

由于 (u

分类讨论:

(x in [0,+infty])

(|x|^P - |x+z|^P = x^P - (x+z)^P)

导数是 (Px^{P-1} - P(x+z)^{P-1})

显然是小于等于 (0) 的。

2.(x in (-infty, 0)) 且 (P) 为偶数

(|x|^P - |x+z|^P = x^P - (x+z)^P)

导数依然是 (Px^{P-1} - P(x+z)^{P-1}), 由于 (P-1) 是奇数, 所以依然是小于等于 (0) 的。

3.(x in (-infty, 0)) 且 (P) 为奇数, (x+z ge 0)

(|x|^P - |x+z|^P = -x^p - (x+z)^P)

导数为 (-Px^{P-1} - P(x+z)^{P-1})

显然是小于等于 (0) 的。

4.(x in (-infty, 0)) 且 (P) 为奇数, (x+z < 0)

(|x|^P - |x+z|^P = -x^p + (x+z)^P)

导数为 (-Px^{P-1} + P(x+z)^{P-1})

显然 (x+z ge x), 但 (x+z) 为负数, 大于 (x) 的负数中没有绝对值比 (x) 大的, 故这个导数也是小于等于 (0) 的。

Q.E.D.

可以放心用决策单调性优化了。

实现方法

二分栈

从左往右扫, 用扫到的状态更新它后面的状态。由于一个状态只会从它左边的状态转移来, 所以此算法的正确性得以保证。

由于决策单调性, 每次遭到更新的状态集一定是序列的一段后缀, 可以快速计算。

具体实现的时候用栈维护几个连续的段, 每个段记录其左端点,就可以描绘出整个转移序列。每扫到 (i) 的时候, 先把 (i) 的 (dp) 值计算出来, 再用其更新后面状态的转移。

实现的时候有几个关键点, 决定着程序的常数。

以 诗人小G 这道题为例。

首先是一个糟糕的实现, 虽然能过, 但是耗时并不优秀。

(由于没有写注释, 观看的时候只看代码的丑陋程度就行了)

//对于每段不仅维护了左端点还维护了右端点, 并且加入了繁杂的分类讨论

#include

using namespace std;

const int maxn = 1e5 + 5;

int n,stn,mdzz;

char s[maxn][35];

int S[maxn];

int tot, q[maxn], l[maxn], r[maxn];

long double f[maxn];

long double ksm(long double a, int b) {

long double res = 1;

for(;b;b>>=1, a*=a)

if(b&1) res *= a;

return res;

}

long double val(int pr, int nx) {

long double res = f[pr];

// nx - pr + S[nx] - S[pr] - stn

res += ksm(abs(S[nx]-S[pr] + (nx-pr-1) - stn), mdzz);

return res;

}

int pre[maxn];

void fuck(int i)

{

int L=1, R=tot;

while(L!=R) {

int mid = (L+R+1) >> 1;

if(l[q[mid]] > i) R = mid-1;

else L = mid;

}

int pr = q[L];

pre[i] = pr;

f[i] = val(pr, i);

//cout << pr << ' ';

}

void print(int x)

{

if(!x) return;

int pr = pre[x];

print(pr);

for(int i=pr+1; i

printf("%s

", s[x]);

}

int main() {

int t;

cin >> t;

while(t--)

{

scanf("%d%d%d", &n, &stn, &mdzz);

for(int i=1; i<=n; ++i) {

scanf("%s", s[i]);

S[i] = S[i-1] + strlen(s[i]);

}

q[tot=1] = 0;

l[0]=1, r[0]=n;

for(int i=1; i

fuck(i);

int L=1, R=tot;

while(L!=R) {

int mid = (L+R+1) >> 1;

if(val(i,l[q[mid]]) < val(q[mid],l[q[mid]])) R = mid-1;

else L=mid;

}

int nowb = L;

L=l[q[nowb]], R=r[q[nowb]];

while(L!=R) {

int mid = (L+R) >> 1;

if(val(i, mid) < val(q[nowb], mid)) R=mid;

else L=mid+1;

}

int nowp = L;

if(val(i, nowp) > val(q[nowb], nowp)) ++nowp;

if(nowp == n+1) continue;

while(l[q[tot]] > nowp) --tot;

if(l[q[tot]] == nowp) q[tot]=i, l[i] = nowp, r[i] = n;

else {

r[q[tot]] = nowp-1;

q[++tot] = i;

l[i] = nowp;

r[i] = n;

}

}

fuck(n);

if(f[n] <= 1e18) {

cout << (long long)f[n] << '

';

//print

print(n);

}

else cout << "Too hard to arrange

";

cout << "--------------------

";

}

return 0;

}

接下来是比较优美的实现。

//这份实现充分体现了二分栈算法的特性, 理解这份实现对更好理解二分栈算法有帮助

#include

using namespace std;

const int N = 1e5+5;

int n,l,p;

char s[N][33];

int a[N];

long double ksm(long double x, int b) {

long double res = 1;

for(;b;b>>=1, x=x*x)if(b&1) res*=x;

return res;

}

long double dp[N];

long double val(int j, int i) {

return dp[j] + ksm(abs(i-j-1+a[i]-a[j]-l), p);

}

int fr[N], lp[N], tp, tra;

int fid(int x) {

int l = lp[tp], r=n+1;

while(l!=r) {

int mid = (l+r) >> 1;

if(val(x,mid) < val(fr[tp],mid)) r=mid;

else l = mid+1;

}

return l;

}

void solve() {

memset(lp,0,sizeof lp);

tp = tra = 1;

lp[1]=1, fr[1]=0;

for(int i=1;i<=n;++i) {

if(i==lp[tra+1]) ++tra;

dp[i] = val(fr[tra], i);

while(lp[tp]>i && val(i, lp[tp]) < val(fr[tp],lp[tp]) ) --tp;

int tmp = fid(i);

if(i<=n) ++tp, fr[tp]=i, lp[tp]=tmp;

}

}

int pre[N];

void Prin(int i) {

if(!i) return;

Prin(pre[i]);

for(int j=pre[i]+1;j

printf("%s

", s[i]);

}

void print() {

if(dp[n]>1e18) puts("Too hard to arrange");

else {

cout << (long long)dp[n] << '

';

// 这里偷懒写了大常数 owo

// awsl

int r = n;

while(tp) {

while(r>=lp[tp]) pre[r--]=fr[tp];

--tp;

}

Prin(n);

}

}

int main() {

int t; cin>>t; while(t--) {

memset(a,0,sizeof a);

scanf("%d%d%d",&n,&l,&p);

for(int i=1;i<=n;++i) {

scanf("%s",s[i]); a[i]= a[i-1] + strlen(s[i]);

}

a[n+1] = a[n] + 1;

solve();

print();

puts("--------------------");

}

return 0;

}

啊这, 还是两格空格缩进好看, 完全不一样的feel啊。

a41c16475457d03ffad8799f3402a09c.png

分治

有点难用, 不写了。

单调队列(太简单了不写了)

单调队列优化多重背包

斜率优化

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值