单调队列优化dp小结
我癌症晚期级别的dp力还敢写起小结来了,令人忍俊不禁。
显然这是一道dp。
显然有方程
k是区间长。
答案为
传统方法对
为了方便框住区间长,队列中的元素一般为下标,处理区间的时候用
单调队列即保证单调性的队列。因此,
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
int n,m,ans=1e9;
int w[MAXN];
int dp[MAXN];
int q[MAXN],t,h;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
for(int i=1;i<=n;i++){
while(t>=h&&q[t]-q[h]+1>m)++h;
dp[i]=dp[q[h]]+w[i];
while(t>=h&&dp[i]<dp[q[t]])--t;
q[++t]=i;
if(i>=n-m+1)ans=min(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
一般情况下单调队列优化dp的步骤如下:
- 处理区间大小,即弹队头,生成转移用最优解
- 用最优解进行状态转移
- 将当前下标加入队列成为待选解,也是处理最优解的过程
在代码也有体现。
分析一下,对于第
- 扩大当前选奶牛方案的效率
- 扩大连续奶牛数超过k的可能性
因此使用
如果不选这头牛,那么最大效率没有变化:
如果选择了这头牛:
我们不妨假设不仅选择了这头牛,还选择了从某头牛 j 以来的所有牛。当然,要保证
按照上文分析的步骤打码。
#include<bits/stdc++.h>
#define int long long
#define MAXN 100005
using namespace std;
int n,m;
int sum[MAXN];
int q[MAXN],h,t;
int dp[MAXN][2];
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
for(int i=1;i<=n;i++){
while(t>=h&&q[t]-q[h]+1>m)++h;
dp[i][0]=max(dp[i-1][1],dp[i-1][0]);
dp[i][1]=dp[q[h]][0]+sum[i]-sum[q[h]];//选这头牛的最大效率就是自从j不选以来都选的最优解的最优解j`
while(t>=h&&dp[i][0]-sum[i]>dp[q[t]][0]-sum[q[t]])--t;//上文中可以发现,前缀和部分是无法从max函数中提取出来的。
//也就是说实际的最优答案由不选j以前的最优解和j+1到i的效率和共同组成,加入单调队列处理。
//然而,未来要处理的dp[i][1]=dp[q[h]][0]+sum[i]-sum[q[h]]中,sum[i]是固定但不可预测的。
//所以我们只要让dp[q[h]][0]-sum[q[h]]尽可能大即可,这就是为什么单调队列要维护一个似乎意义不明的负数。
q[++t]=i;
}
printf("%lld",max(dp[n][0],dp[n][1]));//不知道最后一个要不要选,比对一下输出即可。
return 0;
}
首先关于处理钢琴向某个方向滑动的问题,查看题解:
const int dx[5]={0,-1,1,0,0};
const int dy[5]={0,0,0,-1,1};
...
void solve(int len,int dir,int x,int y){
h=0,t=-1;
for(int i=1;;x+=dx[dir],y+=dy[dir],i++){
...
int main(){
...
for(int i=1;i<=n;i++)scanf("%s",mp[i]+1);
for(int i=1;i<=k;i++)scanf("%d%d%d",&opti[i].s,&opti[i].e,&opti[i].dir);
for(int j=1;j<=k;j++){
int l=opti[j].e-opti[j].s+1;
if(opti[j].dir==1)for(int i=1;i<=m;i++)solve(l,opti[j].dir,n,i);
if(opti[j].dir==2)for(int i=1;i<=m;i++)solve(l,opti[j].dir,1,i);
if(opti[j].dir==3)for(int i=1;i<=n;i++)solve(l,opti[j].dir,i,m);
if(opti[j].dir==4)for(int i=1;i<=n;i++)solve(l,opti[j].dir,i,1);
使用了搜索题走迷宫的方法。真他妈聪明
我们假设
如果现在在横坐标上滑动了一段时间:
考虑题目中滑动时间的问题,
纵坐标同理,略。
我们发现本题中时间顺序和施魔法是无意义的,反正又没让输出施魔法方案
另外,为了不讨论移动方向对最优下标
#include<bits/stdc++.h>
#define MAXN 205
using namespace std;
int n,m,X,Y,k;
char mp[MAXN][MAXN];
struct node{
int s,e,dir;
}opti[MAXN];
const int dx[5]={0,-1,1,0,0};
const int dy[5]={0,0,0,-1,1};
int dp[MAXN][MAXN];
int qval[MAXN],qloc[MAXN],h,t;
int ans;
void solve(int len,int dir,int x,int y){
h=0,t=-1;
for(int i=1;;x+=dx[dir],y+=dy[dir],i++){
if(x<1||x>n||y<1||y>m)break;//出界了就不能dp当前方向了,面向题目背景:小精灵在钢琴到达边缘后一直施法直到该时间段结束即可
// printf("nowx:%d nowy:%d\n",x,y);
if(mp[x][y]=='x')h=0,t=-1;//遇到障碍也不能再dp了,但是不影响我们继续转移别的移动情况,所以不用break。
else{
while(h<=t&&qval[t]+i-qloc[t]<dp[x][y])--t;
qloc[++t]=i;
qval[t]=dp[x][y];
while(t>=h&&qloc[t]-qloc[h]>len)++h;
dp[x][y]=qval[h]-qloc[h]+i;
//放本题的原因就在这五行:dp与单调队列的进行顺序是要依题目变化的。
//遇到障碍重置队列后需要用下一个点在上一段时间中的答案来更新它。所以要先放入队列。
//h与t的初始化也有讲究,不过我现在还没明白,如果哪天明白再回来改。
}
}
}
int main(){
scanf("%d%d%d%d%d",&n,&m,&X,&Y,&k);
memset(dp,128,sizeof(dp));//memset只能初始化特殊值,128代表-inf
dp[X][Y]=0;
for(int i=1;i<=n;i++)scanf("%s",mp[i]+1);
for(int i=1;i<=k;i++)scanf("%d%d%d",&opti[i].s,&opti[i].e,&opti[i].dir);
for(int j=1;j<=k;j++){
int l=opti[j].e-opti[j].s+1;
if(opti[j].dir==1)for(int i=1;i<=m;i++)solve(l,opti[j].dir,n,i);
if(opti[j].dir==2)for(int i=1;i<=m;i++)solve(l,opti[j].dir,1,i);
if(opti[j].dir==3)for(int i=1;i<=n;i++)solve(l,opti[j].dir,i,m);
if(opti[j].dir==4)for(int i=1;i<=n;i++)solve(l,opti[j].dir,i,1);
}
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)ans=max(ans,dp[i][j]);
printf("%d",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律