SRM 513 Div2 1000pt: CutTheNumbers
問題概要
W*H(W,H<=4)のボードが与えられて、各セルには0~9までの数字が埋まっている。これを幅か高さが1の断片に分ける。各断片に書かれた数字を上から(or左から)読んで和を取るとき、作れる和の最大値を求める問題。
考えたこと
practiceで通ったコード
#include <vector> #include <string> #include <algorithm> using namespace std; const int MAX_W = 4; bool visited[MAX_W+1][MAX_W+1][MAX_W+1][MAX_W+1]; int memo[MAX_W+1][MAX_W+1][MAX_W+1][MAX_W+1]; vector<string> board; struct CutTheNumbers { int maximumSum(vector <string> _board) { board = _board; return rec(0, (int)board.size(), 0, (int)board[0].length()); } //[h1,h2) * [w1, w2) の開区間で考える int rec(int h_low, int h_high, int w_low, int w_high){ //メモ化処理 if(visited[h_low][h_high][w_low][w_high]){ return memo[h_low][h_high][w_low][w_high]; } visited[h_low][h_high][w_low][w_high] = true; int height = h_high - h_low, width = w_high - w_low; //1列or1行になったときは、そのまま和を取るのが最善 if(height == 1 || width == 1){ int ret = 0; if(height == 1){ for(int w=w_low; w<w_high; w++){ ret = ret*10 + (board[h_low][w] - '0'); } } else{ for(int h=h_low; h<h_high; h++){ ret = ret*10 + (board[h][w_low] - '0'); } } return memo[h_low][h_high][w_low][w_high] = ret; } int ret = 0; //縦方向の分割 for(int mid=h_low+1; mid<h_high; mid++){ ret = max(ret, rec(h_low, mid, w_low, w_high) + rec(mid, h_high, w_low, w_high)); } //横方向の分割 for(int mid=w_low+1; mid<w_high; mid++){ ret = max(ret, rec(h_low, h_high, w_low, mid) + rec(h_low, h_high, mid, w_high)); } return memo[h_low][h_high][w_low][w_high] = ret; } };
後日知人に指摘されて気づいたこと
- まず、こういう垂直方向か水平方向で再帰的に割る割り方をチョコレート割りと呼ぶらしいことを知った。
- これが一般に広く敷衍している言葉なのかどうかは知らないけど、イメージはし易い良い表現だと思った。
- 適切な名前がつくことは考えやすくするためにも重要。
- で、自分は指摘されるまで勘違いしていたのだけど、この問題はチョコレート割りである必要はないらしい。
- 実際にテストに通っているから本当は最大和を満足するチョコレート割りが存在するのかもしれないけど、直感ですぐ分かる話でもない。
- なので、きちんとチョコレート割り以外の分割も考えてやろう。
- チョコレート割り以外のも考えると、各セルは幅1の長方形に含まれるか高さ1の長方形に含まれるかのどちらかなので、2^16通り調べてやれば良い。
BruteForce解
計算量O( (W*H)*2^(W*H) )。
int maximumSum(vector <string> board) { int H = board.size(), W = board[0].length(); int ret = 0; //意味のある切断を全部列挙する for(int bit=0; bit < (1<<(H*W)); bit++){ vector<string> which = board; //各セルが横成分に含まれるのか縦成分に含まれるのか for(int h=0; h<H; h++)for(int w=0; w<W; w++){ int k = h*W + w; which[h][w] = (((bit>>k)&1)?'h':'v'); } ret = max(ret, func(board, which)); } return ret; } //切断の仕方を与えたときに、和を計算する int func(const vector<string>& board, vector<string> which){ int H = board.size(), W = board[0].length(); int ret = 0; for(int h=0; h<H; h++)for(int w=0; w<W; w++)if(which[h][w] != 'x'){ int tmp = 0; if(which[h][w] == 'h'){ //縦 for(int i=h; i<H && which[i][w] == 'h'; i++){ which[i][w] = 'x'; tmp = 10*tmp + (board[i][w] - '0'); } } else if(which[h][w] == 'v'){ //横 for(int j=w; j<W && which[h][j] == 'v'; j++){ which[h][j] = 'x'; tmp = 10*tmp + (board[h][j] - '0'); } } ret += tmp; } return ret; } };
上位陣の回答をまだ見てないので、後で追記する。