Page List

Search on the blog

2011年8月18日木曜日

GCJ 2011 Round2 Spinning Blade復習

問題概要
R*Cの単位ブロックからなる長方形の鉄板がある。この鉄板から1辺がkの正方形を切り出す。さらに、4隅のブロックを切り落として、ブレードを作る。この鉄板は厚さが不均一なため、重心とブレードの中点が一致しないことがある。各ブロックの重さが与えられるので、重心とブレードの中点が一致するような最大のブレードのサイズを求める。

方針
正方形の左上のブロックを選ぶ。これを(x, y)とする。
 (x+k, y+k)が(r, c)を超えない間、kをインクリメント。
  (x, y), (x+k, y+k)のブレードの重心および中点を計算する。一致すれば、答えに反映する。

これで、smallは通る。全体の処理は、O(N^5)。

largeを通すには、計算量を減らす必要がある。(x, y), (x+k, y+k)のブレードの重心計算は、(x, y), (x+k-1, y+k-1)のブレードの重心情報を利用すれば、O(N)で出来る。これは良くやる動的計画。中点は、((x+x+k)/2, (y+y+k)/2)なので、O(1)。これで全体の計算量はO(N^4)に落ちる。結構きわどいが、これでも通る。

Editorial解は、O(N^3)だった。ACrushとかrngさんとかもO(N^3)でやっていた。この方法は、非常に興味深い。
処理の概要は以下のとおり。
  1. (0, 0), (x, y), x in {0,1,..,R-1}, y in {0,1,...C-1}のブレードの重心を予め計算しておく。このとき、(0,0),(x, y)の重心は、(0,0),(x-1,y-1)と、(0,0), (x-1,y)と、 (0,0),(x, y-1)の情報を利用すれば、O(1)で出来る。よって、1.の処理はO(N^2)
  2. (x, y)(x+k, y+k)のブレードの重心は、(x, y) = (0, 0)の場合は、1.をそのまま使える。そうでない場合は、(0,0), (x+k, y+k)と、(0,0),(x+k, y-1)と、(0, 0),(x-1,y+k)と、(0,0),(x-1,y-1)の情報を使ってO(1)で求めることができる。よって2.の処理は、(x,y,k)のループのみとなり、O(N^3)
コードに落とすときの工夫だが、2.の場合分け(x=0とかy=0とか)をごちゃごちゃしなくていいように、上行と左列にdummyのバッファを入れておくとよい。
以下ソース。

  1. int mass[512][512];  
  2. LL rec_m[512][512];  
  3. LL rec_gx[512][512];  
  4. LL rec_gy[512][512];  
  5.   
  6. void gcjMain() {  
  7.     int t;  
  8.   
  9.     cin >> t;  
  10.     REP(ii, t) {  
  11.         int r, c, d;  
  12.   
  13.         cin >> r >> c >> d;  
  14.         vector<string> mtx(r);  
  15.         REP(i, r)  
  16.             cin >> mtx[i];  
  17.   
  18.         REP(i, r) REP(j, c)  
  19.             mass[i][j] = mtx[i][j] - '0';  
  20.   
  21.         memset(rec_m, 0, sizeof(rec_m));  
  22.         memset(rec_m, 0, sizeof(rec_gx));  
  23.         memset(rec_m, 0, sizeof(rec_gy));  
  24.   
  25.         REP(i, r) REP(j, c) {  
  26.             rec_m[i+1][j+1] = rec_m[i][j+1] + rec_m[i+1][j] - rec_m[i][j] + mass[i][j];  
  27.             rec_gx[i+1][j+1] = rec_gx[i][j+1] + rec_gx[i+1][j] - rec_gx[i][j] + i * mass[i][j];  
  28.             rec_gy[i+1][j+1] = rec_gy[i][j+1] + rec_gy[i+1][j] - rec_gy[i][j] + j * mass[i][j];  
  29.         }  
  30.   
  31.         int ret = -1;  
  32.         REP(i, r) REP(j, c) {  
  33.             for (int k = 2; i + k < r && j + k < c; k++) {  
  34.                 LL ms;  
  35.                 LL gx;  
  36.                 LL gy;  
  37.   
  38.                 ms = rec_m[i+1+k][j+1+k] + rec_m[i][j] - rec_m[i+1+k][j] - rec_m[i][j+1+k];  
  39.                 gx = rec_gx[i+1+k][j+1+k] + rec_gx[i][j] - rec_gx[i+1+k][j] - rec_gx[i][j+1+k];  
  40.                 gy = rec_gy[i+1+k][j+1+k] + rec_gy[i][j] - rec_gy[i+1+k][j] - rec_gy[i][j+1+k];  
  41.   
  42.                 ms -= mass[i][j] + mass[i][j+k] + mass[i+k][j] + mass[i+k][j+k];  
  43.                 gx -= i*mass[i][j] + i*mass[i][j+k] + (i+k)*mass[i+k][j] + (i+k)*mass[i+k][j+k];  
  44.                 gy -= j*mass[i][j] + (j+k)*mass[i][j+k] + j*mass[i+k][j] + (j+k)*mass[i+k][j+k];  
  45.   
  46.                 if (ms * (i + i + k) == 2 * gx && ms * (j + j + k) == 2 * gy)  
  47.                     ret = max(ret, k+1);  
  48.             }  
  49.         }  
  50.   
  51.         if (ret == -1)  
  52.             printf("Case #%d: IMPOSSIBLE\n", ii+1);  
  53.         else  
  54.             printf("Case #%d: %d\n", ii+1, ret);  
  55.     }  
  56. }  



0 件のコメント:

コメントを投稿