poj 2823 Sliding Window 题解与思考

Sliding Window
Time Limit: 12000MS   Memory Limit: 65536K
Total Submissions: 35230   Accepted: 10400
Case Time Limit: 5000MS

Description

An array of size  n ≤ 10 6 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  knumbers 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. 

Input

The input consists of two lines. The first line contains two integers  n and  k which are the lengths of the array and the sliding window. There are  n integers in the second line. 

Output

There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values. 

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

Source

POJ Monthly--2006.04.28, Ikki

【序言】整个下午都在做这道题。焦头烂额啊~~总算有点感觉了。以下是我的思考、感悟。
【大意】给出N个数(N<=10^6)和K,分别求1~k,2~k-1,……n-k+1~n这几段中的最大值和最小值。
【注明】以下描述默认求最大值,最小值也是同理。
【思路一】典型的线段树题目。(我是为了巩固最近的线段树知识才找到这道题的) 刚开始的时候还是挺纠结的,因为我想不好线段树中的sum应该记录什么?如果只是单单记录最小值的话,倘若出现如下情况:
当前tree中的最大值不能直接赋给find,(因为有可能最大值出现在左侧),而是要重新割开线段再搜寻,效率很低。
找了半天的资料,才发现了正解:记录最大值的下标。(其实感觉也没什么区别啊)
~~~~太伤心了!交上去后竟然TLE了!看来效率真的不太高啊!

线段树代码:(TLE)
#include<stdio.h>
using namespace std;
const int maxn=1000005;
struct arr{int l,r,max,min;}a[maxn*2];
int num[maxn],k,n,i;
void build(int k,int l,int r)
{
  a[k].l=l;a[k].r=r;
  if (l==r)
  {
    a[k].max=l;a[k].min=r;return;
  }
  int mid=(l+r)/2;
  build(k*2,l,mid);
  build(k*2+1,mid+1,r);
  int o1=a[k*2].max;int o2=a[k*2+1].max;
  if (o1>0||o2>0)
  {
    if (o1==0) a[k].max=o2;else if (o2==0) a[k].max=o1;else a[k].max=num[o1]>num[o2]?o1:o2;
  } 
  o1=a[k*2].min;o2=a[k*2+1].min;
  if (o1>0||o2>0) 
  {
    if (o1==0) a[k].min=o2;else if (o2==0) a[k].min=o1;else a[k].min=num[o1]<num[o2]?o1:o2;
  }
}
int find_max(int k,int l,int r)
{
  if (a[k].l>=l&&a[k].r<=r) return a[k].max;
  int mid=(a[k].l+a[k].r)/2;
  int o1=-1;int o2=-1;
  if (l<=mid) o1=find_max(k*2,l,r);
  if (r>mid) o2=find_max(k*2+1,l,r);
  if (o1==-1) return o2;
  if (o2==-1) return o1;
  return num[o1]>num[o2]?o1:o2;
}
int find_min(int k,int l,int r)
{
  if (a[k].l>=l&&a[k].r<=r) return a[k].min;
  int mid=(a[k].l+a[k].r)/2;
  int o1=-1;int o2=-1;
  if (l<=mid) o1=find_min(k*2,l,r);
  if (r>mid) o2=find_min(k*2+1,l,r);
  if (o1==-1) return o2;
  if (o2==-1) return o1;
  return num[o1]<num[o2]?o1:o2;
}
int main()
{
  scanf("%ld%ld",&n,&k);
  for (i=1;i<=n;i++)
    scanf("%ld",&num[i]);
  build(1,1,n);
  for (i=1;i<=n-k+1;i++)
    printf("%ld ",num[find_max(1,i,i+k-1)]);
  printf("\n");
  for (i=1;i<=n-k+1;i++)
    printf("%ld ",num[find_min(1,i,i+k-1)]);
  return 0;
}

【思路二】这道题其实是典型的RMQ问题。在SYC大牛的指导下,把ST的代码给写好了(嘘!是百度百科上抄的)
简单介绍ST:f[i][j]表示从第i个数开始,连续的2^j次方个数中的最大值。当然,当j=0时,就是那个数本身。实现起来很像区间DP。在这样的预处理之后,每次只要接近O(1)的效率查找(有点像树状数组的lowbit)。点击获取预处理代码
可是无奈地发现,内存只有65536K!残忍的超内存啊!!

ST代码(超内存)
#include <iostream>
#include <cstdio>
#include <cmath>
#include<algorithm>
#define MN 1000005
using namespace std;
int mi[MN][21],mx[MN][21],w[MN];
int n,q;
void rmqinit()
{
  int i,j,m;
  for(i=1;i<=n;i++){mi[i][0]=mx[i][0]=w[i];}
  m=int(trunc(log2(n)));
  for(i=1;i<=m;i++)
  {
    for(j=n;j>=1;j--)
    {
      mx[j][i]=mx[j][i-1];
      if(j+(1<<(i-1))<=n)
        mx[j][i]=max(mx[j][i],mx[j+(1<<(i-1))][i-1]);
      mi[j][i]=mi[j][i-1];
      if(j+(1<<(i-1)<=n))
        mi[j][i]=min(mi[j][i],mi[j+(1<<(i-1))][i-1]);
    }
  }
}
 
int rmqmin(int l,int r)
{
  int m=int(trunc(log2(r-l+1)));
  return min(mi[l][m],mi[r-(1<<m)+1][m]);
}
 
int rmqmax(int l,int r)
{
  int m=int(trunc(log2(r-l+1)));
  return max(mx[l][m],mx[r-(1<<m)+1][m]);
}
 
int main()
{
  cin>>n>>q;
  for(int i=1;i<=n;i++)
    scanf("%d",&w[i]);
  rmqinit();
  for(int i=1;i<=n-q+1;i++)
    printf("%d ",rmqmin(i,i+q-1));
  printf("\n");
  for(int i=1;i<=n-q+1;i++)
    printf("%d ",rmqmax(i,i+q-1));
  return 0;
}

【思路三】通过观察可以发现,每次求都是有规律的。从第一次开始,每次只是去掉一个数再加上一个数。于是我们就想到了单调队列。(其实我也是刚学的)求最大值的思路:我们构造一个递减序列,每次对于一个a[i],从队尾开始向前寻找,找到最前面一个比它大的并替换。在输出环节时,从头开始寻找(开头肯定是最优的),一旦当前队列里的x[i]的入队时间y[i]>=i-k+1,就直接终端循环并输出答案。不用担心时间效率。
然而,交上去后竟然TLE!令人费解!

单调队列代码(TLE)
#include<stdio.h>
using namespace std;
const int maxn=1000005;
int x[maxn],y[maxn],a[maxn],n,k,i,tail,now;
int main()
{
  scanf("%ld%ld",&n,&k);
  for (i=1;i<=n;i++) scanf("%ld",&a[i]);
  x[1]=a[1];y[1]=1;tail=1;now=1;
  for (i=2;i<k;i++)
  {
    while (a[i]<x[tail]&&tail>0) tail--;
    x[++tail]=a[i];y[tail]=i;
  }
  for (i=k;i<=n;i++)
  {
    while (a[i]<x[tail]&&tail>0) tail--;
    x[++tail]=a[i];y[tail]=i;if (now>tail) now=tail;
    while (y[now]<i-k+1) now++;
    if (i<n) printf("%ld ",x[now]);else printf("%ld\n",x[now]);
  }
  x[1]=a[1];y[1]=1;tail=1;now=1;
  for (i=2;i<k;i++)
  {
    while (a[i]>x[tail]&&tail>0) tail--;
    x[++tail]=a[i];y[tail]=i;
  }
  for (i=k;i<=n;i++)
  {
    while (a[i]>x[tail]&&tail>0) tail--;
    x[++tail]=a[i];y[tail]=i;if (now>tail) now=tail;
    while (y[now]<i-k+1) now++;
    if (i<n) printf("%ld ",x[now]);else printf("%ld\n",x[now]);
  }
  return 0;
}

【思路四】这是对三的改进。在SZQ大神的指导下,把上面的now指针改成了队列的head指针,总算A了!

最终AC代码:
#include<cstdio>
using namespace std;
const int maxn=1000005;
int x[maxn],y[maxn],a[maxn],n,k,i,tail,head;
int main()
{
  scanf("%ld%ld",&n,&k);
  for (i=1;i<=n;i++) scanf("%ld",&a[i]);
  x[1]=a[1];y[1]=1;head=1;tail=1;
  for (i=2;i<k;i++)
  {
    while (a[i]<x[tail]&&tail>=head) tail--;
    x[++tail]=a[i];y[tail]=i;
  }
  for (i=k;i<=n;i++)
  {
    while (a[i]<x[tail]&&tail>=head) tail--;
    x[++tail]=a[i];y[tail]=i;
    while (y[head]<i-k+1) head++;
    if (i<n) printf("%ld ",x[head]);else printf("%ld\n",x[head]);
  }
  x[1]=a[1];y[1]=1;head=1;tail=1;
  for (i=2;i<k;i++)
  {
    while (a[i]>x[tail]&&tail>=head) tail--;
    x[++tail]=a[i];y[tail]=i;
  }
  for (i=k;i<=n;i++)
  {
    while (a[i]>x[tail]&&tail>=head) tail--;
    x[++tail]=a[i];y[tail]=i;
    while (y[head]<i-k+1) head++;
    if (i<n) printf("%ld ",x[head]);else printf("%ld\n",x[head]);
  }
  return 0;
}

【附言】poj上的编译器太坑了!最后AC代码我用G++编译时T了,改成C++就A了。可能之前的程序也会过吧。

你可能感兴趣的:(题解,单调队列)