不支持【单点删除】下的尺取法
可以随意转载,但请附带原文地址http://www.cnblogs.com/qswg/p/8620602.html
问题的提出
有些情况下,操作是没有逆的操作,比如max函数。这导致我们没法通过删除端点来移动左指针。
所以直接用普通尺取来求区间最值是不可能的。
那么是否有办法是否在不支持删除区间端点的情况下,依旧进行尺取呢。
答案是有的,而且实现方法很简单,只要用栈做一个缓存就行了。
算法的实现
为了方便讨论,我们假设尺取时每次查询区间[li,ri],应满足li<=li+1且ri<=ri +1
算法描述如下
首先我们声明一下所需的变量:
p1:左指针
p2:右指针
temp :[p1+1,p2】的区间值
st[]:缓存(栈),其中st[i]代表 [p1-i+1,p1]的区间值
len:被缓存区间的长度。
具体流程:
step1:判断要查询区间[li,ri]是否与缓存区间有交集。若没有执行step2,有则执行step3.
step2: 构造缓存区:将p1,p1赋值为ri.并更新temp的值,然后依次将区间[ri,ri],[ri-1,ri],[ri-2,ri],……,[li,ri] 压入st[]这个栈内。 返回[li,ri]的值
step3:(1)对st执行退栈操作,直至栈顶区间的左端点等于li (2)移动右指针至ri,并更新temp(3)返回temp+st.top();
算法的原理很简单,精髓在于用退栈来实现代替左指针的移动,这样就不用进行删除操作了。
算法复杂度分析:首先双指针的移动的复杂度肯定是O(n)的,而我们每次缓存一个区间的复杂度是O(区间长度),而这些缓存区间的是不相交的。所以缓存区间的长度和最多是n。
综上总复杂度依旧是O(n)
实战
现在我们用新的算法来解决区间最值的尺取问题
http://poj.org/problem?id=2823
Time Limit: 12000MS | Memory Limit: 65536K | |
Total Submissions: 66282 | Accepted: 18836 | |
Case Time Limit: 5000MS |
Description
An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example:
The array is [1 3 -1 -3 5 3 6 7], and k is 3.
Window position | Minimum value | Maximum value |
---|---|---|
[1 3 -1] -3 5 3 6 7 | -1 | 3 |
1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
1 3 -1 -3 5 [3 6 7] | 3 | 7 |
Your task is to determine the maximum and minimum values in the sliding window at each position.
AC代码如下(注意:因为数据量很大,千万不要调用任何的cout和print不然输出会慢10倍以上,进而导致运行时间*10-_-||)
1 #include <stdio.h> 2 #include<stack> 3 #define MAXN 2000005 4 #define MAX 0 5 using namespace std; 6 int a[MAXN]; 7 struct Buff 8 { 9 int st[MAXN],temp,len,p1,p2; 10 int type; 11 void init() 12 { 13 14 temp=0; 15 p1=0; 16 p2=0; 17 len=0; 18 } 19 int fun(int x,int y) 20 { 21 if(type==MAX) 22 return max(x,y); 23 return min(x,y); 24 } 25 int query(int l,int r) 26 { 27 if(l>p1) 28 { 29 build(l,r); 30 return st[len]; 31 } 32 else 33 { 34 while(l+len-1>p1) 35 { 36 len--; 37 } 38 while(p2<r) 39 { 40 p2++; 41 temp=fun(a[p2],temp); 42 } 43 return fun(st[len],temp); 44 } 45 } 46 void build(int l,int r) 47 { 48 int i; 49 if(type==MAX) 50 st[0]=-2e9-7; 51 else 52 st[0]=2e9+7; 53 len=0; 54 for(i=r; i>=l; i--) 55 { 56 ++len; 57 st[len]=fun(st[len-1],a[i]); 58 } 59 p1=r; 60 temp=st[0]; 61 p2=r; 62 } 63 } q; 64 inline void Read(int &x) 65 { 66 char c=getchar(); 67 int f=1; 68 x=0; 69 while (c<'0'||c>'9') 70 { 71 if (c=='-') 72 f=-f; 73 c=getchar(); 74 } 75 while (c>='0'&&c<='9') 76 { 77 x=x*10+c-'0'; 78 c=getchar(); 79 } 80 x*=f; 81 } 82 inline void Write(int a) 83 { 84 if(a<0) 85 { 86 putchar('-'); 87 a=-a; 88 } 89 if(a>9) 90 Write(a/10); 91 putchar(a%10+'0'); 92 } 93 int main() 94 { 95 int n,k,i,x; 96 scanf("%d%d",&n,&k); 97 for(i=1; i<=n; i++) 98 { 99 Read(a[i]); 100 } 101 q.init(); 102 q.type=1; 103 for(i=1; i<=n-k+1; i++) 104 { 105 if(i>1) 106 putchar(' '); 107 x=q.query(i,i+k-1); 108 109 Write(x); 110 } 111 putchar('\n'); 112 q.init(); 113 q.type=0; 114 for(i=1; i<=n-k+1; i++) 115 { 116 if(i>1) 117 putchar(' '); 118 x=q.query(i,i+k-1); 119 Write(x); 120 } 121 putchar('\n'); 122 return 0; 123 }
本题一个比较经典的解法是单调队列,现在我们对比一下两种算法的运行情况。
可以看到单调队列稍快且代码量较少,不过这只是因为我对单调队列没有进行封装而导致的。
所以无论是从空间,时间,还是编程复杂度上,该算法和单调队列都十分相近的,效率十分优秀。
总结
算法的优点:
本文的算法与单调队列一样是用于处理序列尺取的算法,但是单调队列只能处理最值问题的尺取。而本文的算法几乎能处理任何区间尺取问题(只要单点添加操作是O(1)的)。
所以更具有通用性
算法的缺点:
实现起来比单调队列麻烦得多,而且蕴含的区间信息很少,不像单调队列蕴含着偏序关系,所以可能没法实现某些查询;
所以最值的尺取问题还是上单调队列比较好。
习题
http://www.fjutacm.com/Problem.jsp?pid=3260
(因为OJ的网页数据较大,第一次打开要花较长时间)