いもす法
Latest Author
原本
いもす法
想定 初期値0の縦R行、横C行の二次元配列Aに 以下のクエリをQ回行う クエリ:矩形(R1,C1)-(R2,C2)に定数dを足す。 この計算をO(Q + R*C * R*C)で計算する。 元々二次元配列Aに、0以外の値が設定されている場合、 別のアルゴリズムを考えた方が良い。 (Aから勾配配置にO(R*C*R*C)で戻すなど工夫が必要) -------------------- 2次元配列 矩形領域に定数dを足す。 矩形領域は(R1,C1)-(R2,C2)で表すことにします。 R1<=R2 && C1<=C2 ナイーブな実装矩形の総和は、こっちのが詳しい ぱいざ の「O(H^2 W^2 ) の解法」を参照for(int r=R1;r<=R2;r=r+1){ for(int c=C1;c<=C2;c=c+1){ A[r][c] = A[r][c] + d; } }
いもす法は大きく分けて 二つのステップから実行される 1.勾配の配置 2.累積和 例1 縦R行、横C列の2次元配列Aにおいて 矩形(1,1)-(3,3)に1を加算するpos1[0] = 1;//row pos1[1] = 1;//col pos2[0] = 3;//row pos2[1] = 3;//col int add = 1;//加算する値 for(int s=0;s<(1 << 2);++s){ int point[2];//(r,c):勾配を設置する位置 int count = 0; for(int i=0;i < 2;++i){ if( ( s&(1 << i) ) == 0){ point[i] = pos1[i]; }else{ ++count; point[i] = pos2[i]+1;//要:配列レンジチェック } } if( count%2 == 0 ){//パリティチェック A[ point[0] ][ point[1] ] += add; }else{ A[ point[0] ][ point[1] ] -= add; } }
/* やっていること A[1][1]に1加算 A[3+1][1]に-1加算 A[1][3+1]に-1加算 A[3+1][3+1]に1加算 */ 初期値 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 勾配の配置 0 0 0 0 0 0 1 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 -1 0 0 1 //c+方向への累積和:A[r][c] += A[r][c-1]for(int r=0;r < R;r=r+1){ for(int c=1;c < C;c=c+1){ A[r][c] += A[r][c-1]; } }
0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 -1 -1 -1 0 //r+方向への累積和:A[r][c] += A[r-1][c]for(int c=0;c < C;c=c+1){ for(int r=1;r < R;r=r+1){ A[r][c] += A[r-1][c]; } }
0 0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 矩形(1,1)-(3,3)に1を加算出来た。 ------- 例2 複数の矩形をまとめて処理できる。 (1,1)-(3,3)に1加算、(2,2)-(3,4)に2加算 勾配の配置, 0 0 0 0 0 0 1 0 0 -1 0 0 2 0 0 (-2:-2は配置されていない) 0 0 0 0 0 0 -1 -2 0 1 (+2:+2は配置されていない) c+方向の累積和 0 0 0 0 0 0 1 1 1 0 0 0 2 2 2 0 0 0 0 0 0 -1 -3 -3 -2 r+方向の累積和 0 0 0 0 0 0 1 1 1 0 0 1 3 3 2 0 1 3 3 2 0 0 0 0 0 計算できた --------------------------- 勾配配置を求める 例1でのいもす法の手順は 1.勾配の配置 2.c+方向の累積和 3.r+方向の累積和 で行った。 累積和を逆に行うことで、勾配の配置を求めることができる。 初期値 0 0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 r+方向の差分for(int c=0;c < C;c=c+1){//cの順番は不問 for(int r=R-1;r > 0;r=r-1){ A[r][c] -= A[r-1][c]; } }
0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 -1 -1 -1 0 c+方向の差分for(int r=0;r < R;r=r+1){//rの順番は不問 for(int c=C-1;c > 0;c=c-1){ A[r][c] -= A[r][c-1]; } }
0 0 0 0 0 0 1 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 -1 0 0 1 (1,1)-(3,3)の勾配配置が得られた。 未知の勾配配置に対しては 実験的考察が必要だと思います。 うまーく差分を求めていって、 少ない個数の勾配配置を得られれば成功です。 今のところ思いついている累積方向は A[r][c] += A[r-1][c]; A[r][c] += A[r][c-1]; A[r][c] += A[r-1][c-1]; A[r][c] += A[r+1][c-1]; 周囲8近傍/2の4パターンです。 きっと他にもある。 注意1 加算する矩形が(1,1)-(3,3)なのか (1,1)-(2,2)なのかはA[r][c]の意味に依存します。 注意2 累積方向によって、配置する勾配は変わります。 -------------------------------- どこまでがいもす法なのか、これがわからない。 一次元配列の累積和を用いて 連続区間の和がO(1)で求められた。 同様に、 二次元配列の累積和を用いて 矩形内の和がO(1)で求められる。 *(三次元の立方区間の和もO(1)で求められる。 (x1,y1,z1)-(x2,y2,z2):パリティ計算は同じ。) *(特殊な座標や特殊な連続区間に対しては、 同様の事が出来るかはわかりません。) 例2で求めた 0 0 0 0 0 0 1 1 1 0 0 1 3 3 2 0 1 3 3 2 0 0 0 0 0 この二次元配列に対して c+方向の累積とr+方向の累積を実行する。 c+方向の累積 0 0 0 0 0 0 1 2 3 3 0 1 4 7 9 0 1 4 7 9 0 0 0 0 0 r+方向の累積 0 0 0 0 0 0 1 2 3 3 0 2 6 10 12 0 3 10 17 21 0 3 10 17 21 出来た累積和配列を使って矩形(2,2)-(3,3)の総和を求めてみます。 B[3][3] + (-B[2-1][3]) + (-B[3][2-1]) +B[2-1][2-1] = 17 + (-3) + (-3) + 1 = 12 0 0 0 0 0 1 1 1 0 1 3 3 0 1 3 3 - 0 0 0 0 0 1 1 1 - 0 0 0 1 0 1 0 1 + 0 0 0 1 (1回余分に引いているので足す) = _ _ _ _ _ _ _ _ _ _ 3 3 _ _ 3 3 矩形(2,2)-(3,3)の総和が累積和から求められました。 矩形(r1,c1)-(r2,c2)の領域の総和を 累積和配列Bを以下のように用いて求められます。 B[r2][c2]-B[r1-1][c2]-B[r2][c1-1] +B[r1-1][c1-1] ただし、累積和の方向はc+,r+