SRM 547 500pt : RectangularSum
問題概要
H*W(H,W<10^6)で、X[i,j] = i*W+jであるような行列がある。部分行列で和がちょうどS(<10^12)になるようなもののうち、面積最小のものを求める問題。
考えたこと
- これもオーバーフローに注意しないと…。
- 何はともあれ区間決めたときの和を計算しよう。左上のマスをX[h,w]、高さA、幅Bとすると、A*B*( (2*h+A-1)*W+(2*w+B-1)) = 2*Sとなる。
- これでAやBが2*Sの約数となることが分かるけど、これじゃあまだ探索空間が広すぎる。
- とはいえ、(2*h+A-1)*W+(2*w+B-1)というのはよい形だ。このままだとアレだけど、(2*w+B-1)の部分が必ずW未満になるようにすれば一意復号できる。
- 後はいろいろ調べて0<=h && h+A<=H && 0<=w && w+B<=Wとなるかどうかで判定すれば何とか行けそう…。
- このままだと一意に復号できないのでh*W+w + (h+A-1)*W + (w+B-1) = 2*S/(A*B)とかで変形するのか?うーん端っこが邪魔過ぎる。
- AとBを全探索できないからA*Bを全探索するのか?
- ダメだこれだと変数の数が全然減らせない。
- hとwを全探索…も全探索できない。
- 結局このまま終了した。
- 後で他の人の解答見たら、AとBについて枝刈り全探索していた。
- あっ、そうか。AとBを適当に決めたらA*B*( (A-1)*W+B))は3乗とか4乗で増えるからすぐにSを越えて枝刈りが効く。なるほど賢い。
- で、これを本番中に思いつくにはどうすればいいんだろう。
acceptされたコード
#include <algorithm> using namespace std; typedef long long int64; const int64 INF = 1LL<<62; struct RectangularSum { int64 minimalArea(int H_, int W_, int64 S) { int64 H = H_, W = W_; S *= 2; int64 ans = INF; for (int64 A = 1; A <= H; ++A ) if( S % A == 0 ) { for (int64 B = 1; B <= W; ++B ) { if( A * B * ((A - 1) * W + (B - 1)) > S ) { break; } if (S % (A * B) != 0) { continue; } int64 T = S / (A * B) - ((A - 1) * W + (B - 1)); if (T & 1) { continue; } T /= 2; int64 h = T / W, w = T % W; if (0 <= h && h + A <= H && 0 <= w && w + B <= W ) { ans = min( ans, A * B ); } } } return ans == INF ? -1 : ans; } };