单调队列

要开始练单调队列了,本身思想是懂了,问题是怎么看出用单调队列的

1.最大子序和

输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。

注意: 子序列的长度至少是 1

这个题首先是要想到用前缀和(我直接硬想怎么把单调队列用进去,泪目)

然后遍历 所有以 i  结尾的情况,

子序列和为s[i]-s[j],要保证 j 在区间 [i-m,i-1] ,然后找到最小的s[ j ]

分析到这里,不就是一个滑动窗口中找最小值吗?

#include<iostream>
#include<cstring>
#include<algorithm>
#define ll long long
#define go(i,a,b) for(int i=a;i<=b;++i)
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
using namespace std;

int n,m;
int s[300000+10];
int q[300000+10];
int main(){
cin>>n>>m;


go(i,1,n){
    scanf("%d",&s[i]);
    s[i]+=s[i-1];
}

int head=1,tail=1;
q[1]=0;
int ans=-1e9;//初始化的时候动动脑子,别像咸鱼一样
go(i,1,n){

while(head<=tail&&i-q[head]>m)++head;
ans=max(ans,s[i]-s[ q[head] ]);

while(head<=tail&&s[i]<=s[q[tail]])--tail;

q[++tail]=i;
}
cout<<ans;

return 0;
}

 

未完待续。。。

 

终于来了!!!

好好的大家闺秀熬成婆

做了一个坑巨多的题

[NOIP2009 普及组] 道路游戏

(洛谷题链)

1.首先环形就够喝一大壶,环形要时刻记得取模,而且还有负数取模的情况

int get(int i){return ((i%n)+n)%n;}

2.二维前缀和,由于是在环形条件,就有一个巨坑,就是先循环行还是先循环列

go(j,1,m){
    go(i,0,n-1){
        sum[i][j]=sum[get(i-1)][j-1]+jb[i][j];
    }
}

 其中sum[i][j]表示从 时刻开始 到 时刻 i ,且在时刻 i 到达了 点 j 的所有金币之和(可以算出起点为 j-i+1 )

也就是 在时刻开始时 ,从点 j-i+1出发,然后走了i个时间,到达了 j 点,sum[i][j]表示的就是其路径上的所有金币之和

 

 

 如果一行一行的枚举,从第一行的第二个点开始,sum[i][j]就是错的,因为sum[get(i-1)][j-1]还没有算,即第一行的前驱还没有算好

而一列一列地枚举,第一列的前驱(即sum[get(i-1)][j-1])为零,第二列的所有前驱都在第一列,即第二列的前驱都算好了,第三列以此类推。所以一列一列地枚举是对的。

 

3.时间的变换和点的变换紧紧关联

在一条路径上,时间的变化  = 点的变化, 所以在一条路径上 知道了时间就知道了点,反之亦然。

(在一条路径上,就是在一条斜线上)

(由在一条斜线上时间和点都在变,在代码中,我用开始时刻的点代表这条斜线)

(某个时刻的终点,这样的终点在使用时有很多,如果用  终点+终点时刻  来表示条斜线,就会有多种表示,计算起来十分复杂)

4.dp单调队列优化

所以说即使想暴力dp做出来也是不容易啊

更难受的是,还有正解是dp单调队列优化(虽然暴力能过,还能剪枝),还有一箩筐的细节。

要用 单调队列,

1)首先是要看出能用(泪目,我没有看出来)

要对那个 长度限制p 敏感,滑动窗口 = 长度限制+滑动,再一看,确实也随着时间滑动

2)看出敏感点之后,又要算出怎么怎么用(没错,是’算‘,要推公式的)

dp[i]表示在时刻 i 时,得到的金币最多的情况。

枚举它前k个时刻的dp(即dp【i-k】),然后再加上最后走的那一段路。

但是问题又来了,当k一定时,最后走的那一段路有多种情况(回退距离一样,终点时间一样(即时刻 i ),终点那个点却不一样),所以还要枚举终点 j

dp[i] = max{dp[i - k] + sum[j, i] - sum[j - k, i - k] - cost[j - k + 1]}

dp[i - k] + sum[j, i] - sum[j - k, i - k] - cost[j - k + 1] =dp[i - k]  - sum[j - k, i - k] - cost[j - k + 1]    + sum[j, i]

可以看出,当  终点时间 和 终点那个点 确定后,方程里的的sum[j, i] 也就一定了,

令g(a,b)=dp[i - k]  - sum[j - k, i - k] - cost[j - k + 1]   (其中a=i-k,b=j-k)

则dp[i] = max{         g(a,b)  +    sum[j, i]   }

只要g(a,b)最大,dp[i]就最大。

在这里,我把g(a,b)看作是 在时刻 a 时b点的某个值,

因为(a,b)= ( i-k , j-k),   要在所有满足条件的k中选出一个最优值,所以(a,b)和 (i,j)在一条斜线上,也就是说,求这条斜线上的  g(a,b)的最大值,  i、j 确定了,斜线也就确定了。

这样说更好,对于一个(i,j),所有的(a,b)都在同一条直线上,在所有(a,b)中找到一个最大值

再次强调,是在这条斜线上的求最大值,跨斜线的比较是没有意义的(笔者没有看清这点,因为这个问题又困了五百年)

(我分别求 集合X 、集合Y的 最大值,在找的时候, 把集合A中的某元素和集合B中的某元素,有意义吗,得到的是什么东西

现在再来看,在斜线上回退k个,回退有固定范围p,还要在固定范围内求最大值,----->滑动窗口 !!!!(当时间增加一个,斜线上的窗口就滑动一个)

 

一些代码细节

//因为 “ 行走次数可以为 1 p 之间的任意整数”,所以不能停下来,(如果可以停下来的话可以取零),答案会出现负数
memset(dp,-999999,sizeof(dp));

 

 

//这是当在某一时刻,枚举所有的斜线,更新单调队列,其中  q[w][j]表示的是一条斜线上的单调队列  ,这条斜线 在时刻零的点是j点,  
go(j,0,n-1){ while(head[j]<=tail[j]&&i+1-q[headj][j]>p)++head[j]; //这里 是 i+1 是因为这个单调队列是给下一个 时刻用的(对于i的下一个循环) while(head[j]<=tail[j]&& g(i,get(j+i-1))>=g(q[tailj][j],get(j+q[tailj][j]-1)) )--tail[j]; q[++tailj][j]=i;// i进入滑动窗口内,给i+1、i+2、。。。、i+p 用(当然,也可能中途被淘汰) }

 

 

 1 #include<iostream>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define go(i,a,b) for(int i=a;i<=b;++i)
 5 #define g(i,j) (dp[i]-sum[j][i]-cost[get(j+1)])
 6 #define headj head[j]
 7 #define tailj tail[j]
 8 
 9 using namespace std;
10 int jb[1000+10][1000+10];
11 int sum[1000+20][1000+10];
12 int cost[1000+21];
13 int dp[1000+2];
14 int n,m,p;
15 int q[1000+2][1000+2];
16 int head[1000+2],tail[1000+2];
17 inline int get(int i){return ((i%n)+n)%n;}
18 inline int max(int a,int b){return (a>b?a:b);}
19 inline int min(int a,int b){return (a<b?a:b);}
20 void f(){
21 memset(dp,-999999,sizeof(dp));
22 dp[0]=0;
23 
24 
25 
26 go(i,1,m){
27     go(j,0,n-1) {
28         int i0=q[headj][j];
29         dp[i]=max(g(  i0,  get(j+i0-1)  )+sum[get(j+i-1)][i],dp[i]);
30         }
31   //printf("%d ",dp[i]);
32     go(j,0,n-1){
33         while(head[j]<=tail[j]&&i+1-q[headj][j]>p)++head[j];
34         while(head[j]<=tail[j]&& g(i,get(j+i-1))>=g(q[tailj][j],get(j+q[tailj][j]-1)) )--tail[j];
35         q[++tailj][j]=i;
36     } 
37 }
38 cout<<dp[m];
39 }
40 
41 int main(){
42 cin>>n>>m>>p;
43 
44 go(i,0,n-1)
45     go(j,1,m){scanf("%d",&jb[i][j]);}
46 go(i,0,n-1)scanf("%d",&cost[i]);
47 go(j,1,m){
48     go(i,0,n-1){
49         sum[i][j]=sum[get(i-1)][j-1]+jb[i][j];
50   //      printf("%d %d: %d\n ",i,j,sum[i][j]);
51     }
52 }
53 
54 f();
55 return 0;
56 }
道路游戏ac代码(单调队列优化)

 

posted @ 2021-12-11 21:14  joyjumpjump  阅读(53)  评论(0)    收藏  举报