SRM 291 DIV1 Hard StickedWords
問題
辞書dictが与えられる。
この辞書dictに含まれる単語を使ってしりとりをする。
ただし、同じ単語を複数使っても良い。
しりとりをして文字列を作ることが出来る。例えば"abc"と"cd"と"efgh"でしりとりすると"abcdefgh"という文字列になる。
その文字列のうち、len文字以上であって辞書順最小のものを求めよ
- 1 <= |dict| <= 50
- 2 <= |dict[i]| <= 50
- dictは英小文字アルファベットからなる
- 1 <= len <= 2500
解答
辞書順greedyっぽくやる。
これからできるかの判定には、アルファベットからアルファベットへの文字数グラフを作って、(len <= 今の文字数 + Max (i='a'..'z') { -dist[最後の文字][i] })のようなことをすればいい。
しかしそれぞれの要素の文字数が違うので単純な辞書順greedyではできない。
ならば文字数ごとに考えればいい。dp[i]をi文字の辞書順最小の文字列とすれば、(dp[i] = Min (w in dict, last dp[i-(|w|-1)] = head w) { dp[i-(|w|-1)] ++ tail w} )というふうにできる。
これの計算量は微妙な気もするが、単純に実装しても最大550ms程度で通る。
dp[長さ][最後のアルファベット]というDPも考えられる(メモリ節約が必要)。最初これの計算量はかなり厳しいと思ったが、それぞれの単語で1つのアルファベットしか関与しないのだから、まったく問題ない
コメント
後者のdpの、一見次元が増えてその分かかってくるかなーって思えるけれど実際にはそんなことない、というのはまあ自明なんだけどなるほどと思えた。
前者はなんかdp[i]の大小を常に持って、appendと比較の部分をなんかうまくやれば各dp[i]がO(|dict| |単語| log (len+|単語|))くらいで出来る気がなんとなーくするけど本当かな?いつか考えてみたい
コード
#include <string> #include <vector> #include <cstring> #define rep(i,n) for(int (i)=0;(i)<(int)(n);++(i)) #define rer(i,l,u) for(int (i)=(int)(l);(i)<=(int)(u);++(i)) #define all(o) (o).begin(), (o).end() #define mset(m,v) memset(m,v,sizeof(m)) #define INF 0x3f3f3f3f using namespace std; struct StickedWords { string constructLine(vector <string> d, int len) { int n = d.size(); static int wf[26][26], maxlength[26]; mset(wf, INF); rep(i, 26) wf[i][i] = 0; rep(i, n) { int a = d[i][0] - 'a', b = d[i][d[i].size()-1] - 'a'; wf[a][b] = min(wf[a][b], - ((int)d[i].size() - 1)); } //負閉路うまくやるのってWFではどうやってやるのがうまいのかわからない rep(k, 26) rep(i, 26) rep(j, 26) if(wf[i][k] < INF && wf[k][j] < INF) wf[i][j] = min(wf[i][j], wf[i][k] + wf[k][j]); rep(k, 26) if(wf[k][k] < 0) { rep(i, 26) rep(j, 26) if(wf[i][k] < INF && wf[k][j] < INF) wf[i][j] = -INF; } rep(i, 26) { maxlength[i] = 0; rep(j, 26) maxlength[i] = max(maxlength[i], - wf[i][j]); } static string dp[2551]; rer(i, 1, len+50) { dp[i] = "~"; rep(j, n) { if(i + maxlength[d[j][d[j].size()-1]-'a'] < len) continue; int prevlen = i - ((int)d[j].size() - 1); if(prevlen <= 0) continue; if(prevlen != 1 && dp[prevlen][dp[prevlen].size()-1] != d[j][0]) continue; string t = prevlen == 1 ? d[j] : dp[prevlen] + d[j].substr(1); dp[i] = min(dp[i], t); } } string r = "~"; rer(i, len, len+50) r = min(r, dp[i]); return r == "~" ? "" : r; } };