单调队列
要开始练单调队列了,本身思想是懂了,问题是怎么看出用单调队列的
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 }

浙公网安备 33010602011771号