カメヲラボ

主にプログラミングとお勉強全般について書いてます

Sliding Window

  • なかなか良さげな問題

http://acm.pku.edu.cn/JudgeOnline/problem?id=2823
巨大な配列(最大10^6)の中で長さkの区間内の最大値と最小値を求める問題。
配列のサイズn=10、区間の長さk=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

このように区切ることになり、最小値・最大値は順に

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

のようになる。配列サイズが大きいので、当然単純なソートでは終わらない。モロにRMQアルゴリズムの問題なのだと思いますが、資料が少ない。っちゅうわけで探しました。最初に見つけたのがodzさんの日記
http://d.hatena.ne.jp/odz/20070319/1174366528
ここから
http://tlas.i.kyushu-u.ac.jp/~bannai/software/misc/
を発見して公開されているコードをそのまま使ってみたわけですが、そのままだと思ったより速くありません(7秒程度)。自分でより単純に書き直したコードの方が少し速い(5秒ちょい)感じです。たぶんコメントアウトされているインラインアセンブリ等でチューニングすれば3〜5秒くらいまでは速くなると思います。しかしstatusを見ると上位は1秒程度というかなりの速度で通っているので、はたしてRMQのチューニングだけでこの速度が出るのか?上位の一人は最速*1であると同時に省メモリでもあるのが非常に興味深い。RMQでなくて、何か素敵なデータ構造と探索アルゴリズムがあるのでしょうか?
とりあえず、普通に書いたコードを載せておきます。

#include<stdio.h>
#include<math.h>

#define MAX(x,y) (x)>(y)?(x):(y)
#define MIN(x,y) (x)<(y)?(x):(y)

int bufMin[2][1000000];
int bufMax[2][1000000];

main() 
{
  int n,i,j,k,r;

  for(;~scanf("%d%d",&n,&k);)
  {
    r=log(1.0*k)/log(2.0);
    // 丸め誤差に気をつけないとダメです。
    // ナニソレという人は下記のトラックバック
    // odzさんの日記をご覧下さい。

    for(i=0;i<n;++i)
    {
      scanf("%d",&bufMin[0][i]);
      bufMax[0][i]=bufMin[0][i];
    }

    for(j=0;j<r;++j)
    {
      for(i=0;i+(1<<j)<n;++i)
      {
        bufMin[j&1^1][i]=MIN(bufMin[j&1][i],bufMin[j&1][i+(1<<j)]);
        bufMax[j&1^1][i]=MAX(bufMax[j&1][i],bufMax[j&1][i+(1<<j)]);
      }
    }
    for(i=0;i<n-k+1;i++)
      printf("%d ", MIN(bufMin[r&1][i],bufMin[r&1][i+k-(1<<r)]));
    puts("");
    
    for(i=0;i<n-k+1;i++)
      printf("%d ", MAX(bufMax[r&1][i],bufMax[r&1][i+k-(1<<r)]));
    puts("");
  }
}
  • 追記

出力で時間かかるんかなーと思って、sprintf+putsにするとGCCでは1秒程速くなりました。でもCだと遅くなっちゃったナァ(;´д`)GCCの中では上位にきたけど、これはおそらく総合上位の人たちがGCCを使っていないだけでしょう。。。
総合のトップはG++ばっかりです。

  • 追記2

良さげな資料
http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor

*1:1位ではないけど数回Submitすれば誤差で1位になれると思う