单调队列优化DP解题报告(uoj转)

news/2024/9/27 9:09:20

Fence

\(K\)很小,考虑\(K\)开一维,\(N\)开一维

\(f_{i,j}\)表示前\(i\)个工匠粉刷前\(j\)个木板的最大花费

\(f_{i,j}=\min_{k=j-l_i}^{s_i-1} f_{i-1,k}+(j-k) \cdot p_i\)

拆开为

\(f_{i,j}=f_{i-1,k}-k \cdot p_i+j \cdot p_i\)

\(i\)固定时维护\(f_{i-1,k}-k \cdot p_i\)的最小值即可

应该维护一个从\(s_i-1\)开始的后缀min,单调队列也行,但数组更香

PS:数据范围是不是错了,调了一年

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 105
#define M 160005
#define ll long long
int n,m,L[N],P[N],S[N];
ll mn[M],f[N][M];
struct node{int l,p,s;
}a[N];
bool cmp(node A,node B){return A.s<B.s;
}
int main(){scanf("%d%d",&m,&n);fr(i,1,n)scanf("%d%d%d",&a[i].l,&a[i].p,&a[i].s);sort(a+1,a+n+1,cmp);fr(i,1,n)L[i]=a[i].l,P[i]=a[i].p,S[i]=a[i].s;fr(i,1,n){memset(mn,-0x3f,sizeof mn);for(int j=S[i]-1;j>=0;j--)mn[j]=max(mn[j+1],f[i-1][j]-1ll*j*P[i]);fr(j,S[i],S[i]+L[i]-1)f[i][j]=mn[max(0,j-L[i])]+1ll*j*P[i];fr(j,1,m)f[i][j]=max(f[i][j],max(f[i-1][j],f[i][j-1]));}printf("%lld\n",f[n][m]);return 0;
}

P2569 [SCOI2010]股票交易

其实不难,嘴巴AC算了

\(f_{i,j}\)表示第\(i\)天持有\(j\)股,当前收益max值

然后就是大分讨,拆式子,单调队列

#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 2021
#define inf 0x3f3f3f3f
int n,m,k,ap[N],bp[N],as[N],bs[N],f[N][N];
deque<int> q;
int main(){scanf("%d%d%d",&n,&m,&k);fr(i,1,n)scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);memset(f,-0x3f,sizeof(f));fr(i,1,n){fr(j,0,as[i])f[i][j]=-j*ap[i];fr(j,0,m)f[i][j]=max(f[i][j],f[i-1][j]);if(i<=k)continue;q.clear();fr(j,0,m){while(q.size()&&j-q.front()>as[i])q.pop_front();while(q.size()&&f[i-k-1][j]+j*ap[i]>=f[i-k-1][q.back()]+q.back()*ap[i])q.pop_back();q.push_back(j);if(q.size())f[i][j]=max(f[i][j],f[i-k-1][q.front()]+(q.front()-j)*ap[i]);}q.clear();for(int j=m;j>=0;j--){while(q.size()&&q.front()-j>bs[i])q.pop_front();while(q.size()&&f[i-k-1][j]+j*bp[i]>=f[i-k-1][q.back()]+q.back()*bp[i])q.pop_back();q.push_back(j);if(q.size())f[i][j]=max(f[i][j],f[i-k-1][q.front()]+(q.front()-j)*bp[i]);}}printf("%d",f[n][0]);return 0;
}

P4954 [USACO09OPEN]Tower of Hay G

题目大意:\(n\)个数划分成若干个连续段,前一段sum\(\geq\)后一段sum,问最多划分几段

一个明显的DP是:
\(f_{i,j}\)表示前\(i\)个,最后一段为\(i\)~\(j\)的最高层数

但很遗憾这样最少也是\(n^2\)无法再优化了

考虑干掉第二维

手玩几组样例后发现,最优解一定是可行解中底部最小的

直觉上就是传上去了,不亏

证明用数归即可

因而只需确认底层最少几个,再递归即可

但还是难转移,因为底层看不到上面

所以只能从上面看下来

因此倒做

考虑\(f_i\)表示\(i\)~\(n\)分好的最高层数

\(g_i\)表示此时底层最小宽度

\(f_i=\max_{j=i+1}^n f_j+1\) if \(s_{i...j-1} \geq g_j\)

\(O(n^2)\)

但是if后的条件\(s_{j-1}-s_{i-1} \geq g_j \leftrightarrow s_{i-1} \leq s_{j-1}-g_j\)

为了好看可改为后缀和

\(s_i-s_j \geq g_j\) , \(s_i \geq g_j+s_j\)

不难看出单调性,上优化即可

不得不说是个好题,值得一讲

#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 100005
int n,a[N],s[N],f[N],g[N];
int q[N],hh,tt;
int main(){scanf("%d",&n);fr(i,1,n)scanf("%d",&a[n-i+1]);fr(i,1,n)s[i]=s[i-1]+a[i];fr(i,1,n){while(hh<=tt&&s[q[hh]]+g[q[hh]]<=s[i])hh++;g[i]=s[i]-s[q[hh-1]],f[i]=f[q[hh-1]]+1;while(hh<=tt&&s[q[tt]]+g[q[tt]]>=s[i]+g[i])tt--;q[++tt]=i;}printf("%d\n",f[n]);return 0;
}

P5665 [CSP-S2019] 划分

一模一样的题。。。

突然发现家里电脑没有int128???

空间实在恶心,88pts算了

#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 40000005
#define ll long long
#define wow __int128
int n,typ,b1,b2,x,y,z,m,mod=(1<<30);
ll s[N],g[N];
wow f[N];
int q[N],hh,tt;
void write(wow x){if(x>(wow)9)write(x/10);putchar(x%10+'0');
}
int get(int i){if(i<=2)return i==1?b1:b2;else{int ans=(1ll*b2*x+1ll*b1*y+1ll*z)%mod;b1=b2,b2=ans;return ans;}
}
int main(){scanf("%d%d",&n,&typ);if(typ==0){fr(i,1,n){scanf("%d",&b1);s[i]=s[i-1]+b1;}}else{scanf("%d%d%d%d%d%d",&x,&y,&z,&b1,&b2,&m);int L=1,R,l,r;fr(i,1,m){scanf("%d%d%d",&R,&l,&r);fr(j,L,R)s[j]=s[j-1]+(get(j)%(r-l+1))+l;L=R+1;}}fr(i,1,n){while(hh<=tt&&s[q[hh]]+g[q[hh]]<=s[i])hh++;hh--;g[i]=s[i]-s[q[hh]],f[i]=f[q[hh]]+(wow)g[i]*g[i];while(hh<=tt&&s[q[tt]]+g[q[tt]]>=s[i]+g[i])tt--;q[++tt]=i;}write(f[n]);return 0;
}

哦,看到某位大神题解,对于压空间深受启发,AC code:

#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 40000005
#define ll long long
#define wow __int128
int n,typ,b1,b2,x,y,z,m,mod=(1<<30);
ll s[N],g[N];
struct node{int id;wow f;
};
deque<node> q;
void write(wow x){if(x>(wow)9)write(x/10);putchar(x%10+'0');
}
int get(int i){if(i<=2)return i==1?b1:b2;else{int ans=(1ll*b2*x+1ll*b1*y+1ll*z)%mod;b1=b2,b2=ans;return ans;}
}
int main(){scanf("%d%d",&n,&typ);if(typ==0){fr(i,1,n){scanf("%d",&b1);s[i]=s[i-1]+b1;}}else{scanf("%d%d%d%d%d%d",&x,&y,&z,&b1,&b2,&m);int L=1,R,l,r;fr(i,1,m){scanf("%d%d%d",&R,&l,&r);fr(j,L,R)s[j]=s[j-1]+(get(j)%(r-l+1))+l;L=R+1;}}node hh,tt;q.push_back((node){0,0});fr(i,1,n){while(q.size()&&s[q.front().id]+g[q.front().id]<=s[i])hh=q.front(),q.pop_front();q.push_front(hh);tt.id=i;g[i]=s[i]-s[hh.id],tt.f=hh.f+(wow)g[i]*g[i];while(q.size()&&s[q.back().id]+g[q.back().id]>=s[i]+g[i])q.pop_back();q.push_back(tt);
//		puts("orz");}write(q.back().f);return 0;
}

acwing 299.裁剪序列

很有趣的一个题

首先还是无脑DP,设\(f_i\)表示\(1\)\(i\)分段最小代价

\(f_i = f_j + maxa(i\)~\(j)\)

st表维护最值,\(O(n^2)\)转移即可

考虑优化,发现一段区间在满足和限制且最大值不变的情况下,一定会尽量扩张

因此可以找出对于\(i\)向左,使最大值改变的所有位置,从左到右记为\(p_1,p_2,...p_k\)

不难看出其单调递减,单调队列即可

\(p_i\)贡献为\(f_{p_i}+p_{i+1}\)

拿个堆维护一下就行

当然还有和限制,特判即可

#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 100005
#define ll long long
int n,a[N],vis[N];
ll m,f[N];
int q[N],hh,tt;
struct node{int fo,la;ll d;friend bool operator < (node A,node B){return A.d>B.d;}
};
priority_queue<node> p;
int main(){scanf("%d%lld",&n,&m);fr(i,1,n){scanf("%d",&a[i]);if(a[i]>m){puts("-1");return 0;}}ll sum=0;int j=0;fr(i,1,n){sum+=a[i];while(sum>m)sum-=a[++j];while(hh<=tt&&q[hh]<j)vis[q[hh++]]=1;while(hh<=tt&&a[i]>=a[q[tt]])vis[q[tt--]]=1;if(hh<=tt)p.push((node){q[tt],i,f[q[tt]]+a[i]});q[++tt]=i;f[i]=f[j]+a[q[hh]];p.push((node){q[tt],i,f[q[tt]]+a[i]});while(p.size()&&(vis[p.top().fo]||vis[p.top().la]))p.pop();if(p.size())f[i]=min(f[i],p.top().d);}printf("%lld\n",f[n]);return 0;
}

总结:状态尽量设计的好看一些,分离各个变量,就能用单调队列了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/65280.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

救园倒计时:救园最后4天

救园目的:园子这三年困难阶段靠贷款维持,救园是为了还掉贷款,度过难关。救园方式: 终身会员计划,会员,捐助,周边。救园之后,一边增加收入来源,一边加快推进园子的商业化救园进展 截止9月27日 08:55终身会员:终身VIP会员名额还剩37个,终身VIP会员名额还剩130个 会员总…

将对象的属性为数值型的转换为String

将对象的属性为数值型的转换为String 1、新建一个类 //注意:此处为待转换的类型,return true 不好用,必须将待转换的类型一一列出using Newtonsoft.Json;namespace WinFormsApp1.Common {public class ToStringConverter : JsonConverter{public override bool CanConvert(T…

《HelloGitHub》第 102 期

兴趣是最好的老师,HelloGitHub 让你对编程感兴趣!简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。github.com/521xueweihan/HelloGitHub这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、Java、Go、C/C++、Swift...让你在短…

弹幕树洞项目功能新增篇

项目地址 项目后端地址:https://github.com/ZyPLJ/ZYTteeHole项目前端页面地址:ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue目前项目测试访问地址:http://tree.pljzy.top/ 注意是http,输成https就访问到博客里面去了。系列文章📖.NET Core搭配V…

Cisco Secure Firewall Threat Defense 7.6.0 发布下载,新增功能概览

Cisco Secure Firewall Threat Defense 7.6.0 发布下载,新增功能概览Cisco Secure Firewall Threat Defense 7.6.0 发布下载,新增功能概览 Firepower Threat Defense (FTD) Software Release 7.6.0 Firepower 1100/3100/4100/4200/9300 Security Appliance 请访问原文链接:h…

【译】通过新的 WinUI 工作负荷和模板改进,深入原生 Windows 开发

我们创建了一个新的 Windows Dev Center 页面,简化了我们的 Getting Started with WinUI 文档,并与 Visual Studio 合作来改善开发人员在工作负荷和模板方面的体验。在 Build 2024 上,WinUI 团队宣布将重新关注 WinUI,将其作为我们推荐的原生 Windows 应用开发的首要应用开…

Windows10永久拒绝升级Win11

一、使用组策略阻止升级到windows11 需要专业版或企业版的Windows 10才能访问组策略编辑器。以下是操作步骤:单击开始菜单,输入gpedit.msc,打开本地组策略编辑器。 导航到“计算机配置”>“管理模板”>“Windows组件”>“Windows更新”>“适用于企业的Windows更…

arcgis怎样把面图层按另一面图层分割

摘自https://jingyan.baidu.com/article/6079ad0e9b5c8428fe86db70.htmlarcgis的桌面软件 主要应用于空间数据处理和管理,工作中往往会遇到要批量分割大量的面状数据,并且要按照其所处面的关系赋值。1、打开ArcMap软件,把两个面图层都加载到视图区域内,如下图2、在工具栏中…