这样就能解决很多其他数据结构要O(nlogn)及以上的复杂度才能解决的问题。而且实现起来也很简单。简单即是一种美。。。
具体的讲解看上面那个blog里的ppt(这个和这个)和百度百科里的讲解。
以下是对那个blog里题目的解题报告。。
单纯的单调队列:
志愿者选拔 O(n)
最最入门的单调队列,而且是很形象的排队问题
新建一个空队列,队列里的每个元素有两个数据,一个是第几个进队的,一个是其身高。
对于C操作,从队尾逐个比较其与新加入人的身高,若队尾的那个元素的身高低于新人的身高,则让其出队,一直这样做直到队尾的元素身高大于此人身高,或者队列为空。然后将此人添加到队尾。
对于Q操作,直接将队首元素的身高输出。
对于G操作,比较队首元素的入队次序与当前轮到第几个人出队,判断其是否要出队。
代码如下
#include <iostream> #include <stdio .h> #include <string .h> #define N 1001000 using namespace std; struct data { int pos,value; //pos第几个进队的,value身高 }q[N]; int main() { int t,head,tail,i,j,a; char st[10]; scanf("%d",&t); while( t-- ) { scanf("%s",st); head = 0 ; tail = -1; //tail<head 代表队列为空 i = j = 0; //i为进队的总人数,即下一个进队的人的pos,j为出队的人数,即当前该出队的人的pos while(scanf("%s",st),strcmp(st,"END")) { if(st[0] == 'C') { scanf("%s %d",st,&a); while(head <= tail && q[ tail ].value < a) tail --; //踢掉队尾的元素来保证value的单调性 q[ ++tail ].value = a; //将新人加入,更新value和pos q[ tail ].pos = i++; } else if(st[ 0 ] == 'Q') { if( head > tail ) puts("-1"); //队列为空则输出-1 else printf("%d\n",q[ head ].value); //否则输出队首的元素 } else { j++; while(head < = tail && q[head].pos < j)head++; //将队首该出队的出队,其实这里写成if就行 } } } return 0; }
同上题
查询一系列区间里最大元素的值,这些区间长度相同,左值从小到大。
新建一个空队列,每个元素两个值,位置 和 大小。
先把第一个区间里的数入队,入队规则:比较队尾元素与要入队元素的大小,小则将队尾元素出队,一直重复这个过程直到队为空,或者队尾元素大于当前要入队元素,把此元素插入队尾。
对于后面的区间,先看队首元素是否已经出了当前区间,然后插入新元素。
每次查询输出队首元素。
代码如下:
#include <iostream> #include <stdio .h> #define N 1000100 using namespace std; struct data { int value,pos; }q[ N ]; int a[ N ]; int main() { int n,m,head,tail,i; while(scanf("%d %d",&n,&m)==2) { head = 0; tail = -1; for( i = 0 ; i < n ; i ++ ) { scanf("%d",a+i); } //求每个区间最小值 for( i = 0 ; i < m - 1 ; i ++) { while( head <= tail && q[ tail ].value > a[ i ] ) tail--; q[ ++tail ].value = a[ i ]; q[ tail ].pos = i; } for( ; i < n ; i ++ ) { while( head <= tail && q[ tail ].value > a[ i ] ) tail--; q[ ++tail ].value = a[ i ]; q[ tail ].pos = i; if( q[ head ].pos < = i - m ) head++; printf("%d",q[ head ].value); if( i == n-1)putchar('\n'); else putchar(' '); } //求每个区间最大值 head = 0; tail = -1; for( i = 0 ; i < m - 1 ; i ++) { while( head <= tail && q[ tail ].value < a[ i ] ) tail--; q[ ++tail ].value = a[ i ]; q[ tail ].pos = i; } for( ; i < n ; i ++ ) { while( head <= tail && q[ tail ].value < a[ i ] ) tail--; q[ ++tail ].value = a[ i ]; q[ tail ].pos = i; if( q[ head ].pos <= i - m ) head++; printf("%d",q[ head ].value); if( i == n-1)putchar('\n'); else putchar(' '); } } return 0; }
Max Sum of Max-K-sub-sequenceO(n)
差不多同上题,不过就是先求[1,i]的和,然后循环的,延迟一倍处理一下
先写出dp方程
dp[ i ] = max( sum[ i ] - sum[ j ] ) = sum[ i ] - min( sum[ j ] );
sum[ i ]表示前i个元素的和。
由dp方程可以看出队列要存sum的值,有小到大排列。
出队则判断长度是否大于k了。
#include <iostream> #include <stdio .h> #define N 200010 using namespace std; struct data { int pos,value; }q[ N ]; int a[N]; int sum[N]; int main() { int t,i,n,k,head,tail,ans,ti,tj; scanf("%d",&t); while( t--) { scanf("%d %d",&n ,&k); for( i = 1 ; i < = n ; i ++ ) { scanf("%d",a+i); a[ i + n ] = a[ i ]; } n <<= 1; sum[ 0 ] = 0; for( i = 1 ; i <= n ; i ++ ) sum[ i ] = a[ i ] + sum[ i - 1 ]; head = tail = 0; q[ 0 ].pos = 0; q[ 0 ].value = 0; ans = a[ 1 ] - 1; for( i = 1 ; i <= n ; i ++ ) { if( q[ head ].pos + k < i ) head++; if( sum[ i ] - q[ head ].value > ans ) { ans = sum[ i ] - q[ head ].value; ti = q[ head ].pos; tj = i - 1; } while( head < = tail && q[ tail ].value >= sum[ i ])tail--; q[ ++tail ].pos = i; q[ tail ].value = sum[ i ]; } n >>= 1; printf("%d %d %d\n",ans,ti % n + 1,tj % n + 1); } return 0; }
单调队列优化的DP:
Trade O(n)
本来是O(MaxP *MaxP *T*T)的,T的那一维比较好优化,要取W前天最好的,那么每次都记录前W天最好的,而MaxP那一维的话我是买入做一次单调队列,卖出再做一次.最后复杂度O(Maxp*T)
这个就有点难度了。
还是要先写出dp方程
dp[ i ][ j ] = max( dp[ k ][ j ] , dp[ k ][ j - p ] - ap[ i ] * p , dp[ k ][ j + q ] + bp[ i ] * q );
表示第i天有j个stock拥有的钱的最大值(可能是负值,代表欠着钱)0 < k < i - m , 0 <= p <= as[ i ] , 0 <= q <= bs[ i ]
可见时间复杂度为O(Maxp*MaxP*T*T);
然后我们来降低复杂度:
先减小T那一维复杂度
dp[ i ][ j ]也就是前i天中,有j个stock拥有钱的最大值。
故求dp[ i ][ j ] 与 dp[ i - 1 ][ j ] 只有一天之差,即求完第i - 1天后,已经找完了最大的那个dp[ k ][ j ],只需比较i - m - 1那天的j,即选max(dp[ k ][ j ] , dp[ i - m - 1 ][ j ]),不妨用一个数组g来表示前几天的值,g[ i ]表示有j个stock拥有的钱的最大值。对于每次i,只需更新一遍g即可,而不用k的那重循环来找前k天的最大值。这样复杂度减小了一个T
接着利用单调队列降低p那层复杂度
将求dp[ i ][ j ]分为三个步骤:
dp[ i ][ j ] = max( dp[ k ][ j ] ,dp[ i ][ j ]);
dp[ i ][ j ] = max( dp[ k ][ j - p ] - ap[ i ] * p , dp[ i ][ j ]);
dp[ i ][ j ] = max( dp[ k ][ j + q] +bp[ i ] * q , dp[ i ][ j ]);
求第三个过程和第二个类似,下面重点说第二个过程。
求dp[ i ][ j ]与求dp[ i ][ j - 1 ]有很多重合的部分:
<ul>
<li>求dp[ i ][ j - 1 ]时p的范围是 [ j - 1 - as[ i ] , j - 1 ]</li>
<li>求dp[ i ][ j - 1 ]时p的范围是 [ j - as[ i ] , j ]</li>
此时,去掉i那一维,现在这个样子就和上面的题目差不多了吧,查询区间的最大值,区间的性质也跟上一题相同,但你这么就开始做了就错了(我就这么错的)。这里有点不同啊,就是ap[ i ] 后多乘了一个p,而p是可变的,怎样去维护那个单调队列呢?
先不说怎么去维护,此时想到用单调队列就已经将p那一维优化掉了。
现在说怎么维护队列,比较队列中元素与要新加入元素的关系,他们对于dp[ i ][ j ]的维护就差在那个p上,而两个p值之差就是j值之差,即:
g[ j1 ] - ap[ i ] * p1 j1 + p1 = j 和
g[ j2 ] - ap[ i ] * p2 j2 + p2 = j
故入队时,不能只比较g[ j ]的大小,还要算上j的差值*ap[ i ]。。。
while( head < = tail && q[ tail ].value <= g[ j ] + p[ i ].ap * ( j - q[ tail ].pos ) ) tail--;
这样复杂度就降低到maxP*T了,可见这个优化是很NB的。。。。
代码如下:
#include <iostream> #include <stdio .h> #include <string .h> #define N 2010 using namespace std; struct data { int ap, bp, as, bs; void input() { scanf("%d %d %d %d", &ap, &bp, &as, &bs); } }p[ N ]; struct da { int pos,value; }q[ N ]; int f[ N ][ N ]; int g[ N ]; int main() { int t,n,maxP,w,i,j,head,tail,ans; //freopen("data.txt","r",stdin); scanf("%d",&t); while(t--) { scanf("%d %d %d", &n, &maxP, &w); for( i = 0 ; i < n ; i ++ ) { p[ i ].input(); } ans = 0; memset(f, 131 , sizeof( f )); memset(g, 131 , sizeof( g )); g[ 0 ] = 0; for( i = 0 ; i <= w ; i ++ ) { for( j = 0 ; j <= maxP && j <= p[ i ].as ; j ++ ) f[ i ][ j ] = - p[ i ].ap * j; } tail = 0; for( ; i < n ; i ++ ) { for( j = 0 ; j <= maxP ; j ++ ) if( g[ j ] < f[ i - w - 1 ][ j ] ) g[ j ] = f[ i - w - 1 ][ j ]; head = 0; tail = -1; for( j = 0 ; j <= maxP ; j ++ ) { if( head <= tail && q[ head ].pos + p[ i ].as < j ) head++; while( head <= tail && q[ tail ].value <= g[ j ] + p[ i ].ap * ( j - q[ tail ].pos ) ) tail--; q[ ++tail ].pos = j; q[ tail ].value = g[ j ]; if( f[ i ][ j ] + p[ i ].ap * ( j - q[ head ].pos) < q[ head ].value ) f[ i ][ j ] = q[ head ].value - p[ i ].ap * ( j - q[ head ].pos); } head = 0; tail = -1; for( j = maxP ; j >= 0 ; j -- ) { if( head < = tail && q[ head ].pos - p[ i ].bs > j ) head++; while( head < = tail && q[ tail ].value <= g[ j ] - p[ i ].bp * ( q[ tail ].pos - j ) ) tail--; q[ ++tail ].pos = j; q[ tail ].value = g[ j ]; f[ i ][ j ] = max( f[ i ][ j ] , q[ head ].value + p[ i ].bp * ( q[ head ].pos - j )); } ans = max( ans, f[ i ][ 0 ] ); } printf("%d\n", ans); } return 0; }