2024/12/14课堂记录

今天是我生日!!!

目录

  1. 扫描
  2. 烽火传递
  3. 最大连续和(作业)

单调队列模板题

先上代码
 #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;
}

 

posted @ 2024-12-21 18:59  永韶  阅读(3)  评论(0编辑  收藏  举报