2024/12/14课堂记录
今天是我生日!!!
目录
- 扫描
- 烽火传递
- 最大连续和(作业)
单调队列模板题
先上代码
#include<iostream>
using namespace std;
int a[2000010],q[2000010];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
int h=0,t=0;
//队尾在前面跑,对头在后面追
for(int i=1;i<=n;i++)
{
while(h<t&&q[h]+k<=i)h++;//木板不够长,被迫丢掉
while(h<t&&a[q[t-1]]<a[i])t--;//新来的更大,比他小的全踹掉
q[t++]=i;//新来一个
if(i>=k)cout<<a[q[h]]<<"\n";
}
return 0;
}
理解不了就大暴力自己当机器循环走一遍,就懂了
5 3
1 5 3 4 2
n=5,k=3;
a[]={0,1,5,3,4,2};
h=0,t=0;
for
i=1:h==t,X||h==t,X||q[0]=1,t=1||1<3,X; 从这可以看出队尾入队(新来的)
i=2:0<1,1+3>2,X||0<1,1<5:t=0||q[0]=2,t=1||2<3,X; 从这可以看出新来的踹掉以前的
i=3:0<1,2+3>3,X||0<1,5>3,X||q[1]=3,t=2||3=3:cout<<5; 从这里可以看出新来的没实力,踹不掉以前的
i=4:0<2,2+3>4,X||0<2,3<4:t=1||q[1]=4,t=2||4>3:cout<<5; 从这里可以看出输出的是最前头的
i=5:0<2,2+3==5:h=1||1<2,4>2,X||q[2]=5,t=3||5>3:cout<<4; 从这里可以看出木板不够长丢掉前面的
一种方法是dp
前m个一步到位,直接点亮
后面的就是前面m个中,最小值+他的消费
注意下:最后m个有一个亮就行,不一定是最后一个亮
代码比较简单
#include<iostream>
using namespace std;
int dp[201005],a[201005];
int n,m,ans=0x3f3f3f3f;
// dp[i]: 表示前i个烽火台中,必须包含第i个烽火台的总最小代价
// dp[i]=min(dp[j]+a[i]); i-m<=j<=i-1
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)dp[i]=a[i];//前m个烽火台一步到位
for(int i=m+1;i<=n;i++)
{
dp[i]=0x3f3f3f3f;
for(int j=i-1;j>=i-m;j--)//循环所有能够一步到i的烽火台
dp[i]=min(dp[i],dp[j]+a[i]);
}
for(int i=n-m+1;i<=n;i++)ans=min(ans,dp[i]);//最后m个烽火台只要有一个亮就行
cout<<ans<<endl;
return 0;
}
这个dp其实可以用单调队列优化
如何看是否用单调队列优化?
首先明确,单调队列是用来优化dp的
且优化的要求是:状态转移方程能写成f[i]=min/max(g(j))+a[i]的形式
如本题:dp[i]=min(dp[i],dp[j]+a[i]);
接下来用到数组模拟队列(但是双端队列):q[]
q里面存的是下标,dp[q[]]
保证q里面存的下标是递增的
而dp[q[]]里边存的初始数据是递增或递减,取决于刚才写的状态转移方程到底是max还是min
具体的套路还是直接看代码吧
#include<iostream>
using namespace std;
int dp[201005],a[201005];
int q[201005];
int n,m,ans=0x3f3f3f3f;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
//for(int i=1;i<=m;i++)dp[i]=a[i];试用单调队列优化直接从头开始,这步不需要了
//----------单调队列优化模板---------------
int l=0,r=0;
for(int i=1;i<=n;i++)//从头开始
{
while(l<r&&i-q[l]>m)l++;//第一步:长度过长越界时强制丢掉队头
dp[i]=dp[q[l]]+a[i];//第二步:dp[i]=min(dp[i],dp[j]+a[i]) (稍作修改) ,计算dp(可省略)
//第三步:如果队尾大于(有的题如“扫描”是小于)当前的值,那么把队尾元素删除,保证序列递减 (递增)
while(l<r&&dp[q[r]]>dp[i])r--;
q[++r]=i;//第四步:每次循环入栈一个
}
//------------------------------------------
for(int i=n-m+1;i<=n;i++)ans=min(ans,dp[i]);
cout<<ans<<endl;
return 0;
}
- 作业:最大连续和
附赠单调队列模版
#include <bits/stdc++.h>
using namespace std;
const int maxN=1000005;
int a[maxN],q[maxN];
// q[]: i < j
// f[i] <f[j] or f[i]>f[j]
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int h=0,t=0;
//head指向真正的队头,tail的前1位指向队尾(tail-1)
//h<t 队列不为空
for(int i=1;i<=n;i++)
{
//保证i加入序列后,区间长度<=k
while(h<t&&q[h]+k<=i)
{
h++;
}
//队尾插入a[i],确保下降 ; t-1指向真正的队尾
while(h<t&&a[q[t-1]]<a[i])
{
t--;
}
q[t++]=i;//入队
if(i>=k)cout<<a[q[h]]<<endl;
}
return 0;
}