// 最適化DP(max-plus)自動生成:愚直解から (max,+) 線形表現 u ⊗ A_{S1} ⊗ ... ⊗ A_{SN} ⊗ v を復元する #include using namespace std; using ll = long long; int INF = 1001001001; ll INFL = 4004004003094073385LL; // (int)INFL = INF, (int)(-INFL) = -INF; #define rep(i, n) for (int i = 0, i##_len = int(n); i < i##_len; ++i) #define repi(i, s, t) for(int i = int(s), i##_end = int(t); i <= i##_end; ++i) // s から t まで昇順 #define repb(set, d) for(int set = 0, set##_ub = 1 << int(d); set < set##_ub; ++set) // d ビット全探索(昇順) #define sz(x) ((int)(x).size()) template inline bool chmax(T & M, const T & x) { if (M < x) { M = x; return true; } return false; } // 最大値を更新(更新されたら true を返す) template inline bool chmin(T & m, const T & x) { if (m > x) { m = x; return true; } return false; } // 最小値を更新(更新されたら true を返す)template inline int getb(T set, int i) { return (set >> i) & T(1); } template inline int getb(T set, int i) { return (set >> i) & T(1); } const ll NEG = LLONG_MIN / 4; // -∞ inline ll addn(ll a, ll b) { return (a <= NEG || b <= NEG) ? NEG : a + b; } // (max,+) の積 inline ll fmtn(ll x) { return x; } //====================== ここを書き換える:愚直解(最大化.不可能なら NEG を返す) ====================== // 文字は '0','1',... .最小化したい問題は「値の符号を反転して」返す(最後に符号を戻す). #ifndef NAIVE_ID #define NAIVE_ID 0 #endif #if NAIVE_ID == 0 ll naive(string s) { int n = sz(s); int res = 0; while (1) { string t(n, '0'); bool end = true; rep(i, n) { if (s[i] == '1') { if (i == n - 1) { ; } else { if (s[i + 1] == '0') { t[i + 1] = '1'; } else { t[i] = '1'; } } end = false; } } s = move(t); if (end) break; res++; } return res; } #elif NAIVE_ID == 1 // 最大部分配列和(空可).'0'→-1, '1'→+1, '2'→-2... 重みは w[c] const ll WT[3] = { -1, 1, 0 }; // 文字 0,1,2 の重み ll naive(const string& s) { ll best = 0; int n = sz(s); rep(i, n) { ll sum = 0; for (int j = i; j < n; j++) { sum += WT[s[j] - '0']; best = max(best, sum); } } return best; } #elif NAIVE_ID == 2 // 隣り合う位置は同時に選べない.選んだ位置の価値 val[c] の総和の最大化(全探索 2^n) const ll VAL[2] = { 3, -1 }; ll naive(const string& s) { int n = sz(s); ll best = 0; rep(mask, 1 << n) { if (mask & (mask >> 1)) continue; ll sum = 0; rep(i, n) if (mask >> i & 1) sum += VAL[s[i] - '0']; best = max(best, sum); } return best; } #elif NAIVE_ID == 3 // T との LCS(T = "0110") ll naive(const string& s) { const string T = "0110"; int n = sz(s), m = sz(T); vector> dp(n + 1, vector(m + 1, 0)); rep(i, n) rep(j, m) dp[i + 1][j + 1] = max({ dp[i][j + 1], dp[i + 1][j], dp[i][j] + (s[i] == T[j]) }); return dp[n][m]; } #elif NAIVE_ID == 4 // 選んだ部分列の中で同じ文字が3連続しないように部分列を選び,価値 val[c] の総和を最大化(全探索 2^n) const ll VAL[2] = { 2, 1 }; ll naive(const string& s) { int n = sz(s); ll best = 0; rep(mask, 1 << n) { string t; ll sum = 0; rep(i, n) if (mask >> i & 1) { t += s[i]; sum += VAL[s[i] - '0']; } bool ok = true; rep(i, sz(t) - 2) if (t[i] == t[i + 1] && t[i + 1] == t[i + 2]) ok = false; if (ok) best = max(best, sum); } return best; } #endif //============================================================================================== unordered_map memo; ll H(const string& s) { auto it = memo.find(s); if (it != memo.end()) return it->second; return memo[s] = naive(s); } using Row = vector; // r が G の (max,+) 線形結合で書けるか判定する.留数(principal solution)λ_k = min_z (r[z] - G_k[z]) を求め, // max_k(λ_k + G_k) == r なら成功.skip 番目の生成元は使わない. bool residuate(const Row& r, const vector& G, int skip, vector& lam) { int K = sz(G), m = sz(r); lam.assign(K, NEG); Row rec(m, NEG); rep(k, K) { if (k == skip) continue; ll mn = LLONG_MAX; rep(z, m) if (G[k][z] > NEG) mn = min(mn, r[z] <= NEG ? NEG : r[z] - G[k][z]); if (mn == LLONG_MAX) continue; lam[k] = mn; rep(z, m) rec[z] = max(rec[z], addn(lam[k], G[k][z])); } return rec == r; } // 行の有限成分すべてから t を引く(-∞ はそのまま) Row shifted(Row r, ll t) { for (auto& x : r) if (x > NEG) x -= t; return r; } // principal solution は窓の外では過大になりうる(最大の解だから).表現に不要な係数を -∞ に落として疎にする. void prune(vector& lam, const Row& r, const vector& G) { int K = sz(G), m = sz(r); vector ord; rep(k, K) if (lam[k] > NEG) ord.push_back(k); auto wit = [&](int k) { int w = 0; rep(z, m) if (G[k][z] > NEG && addn(lam[k], G[k][z]) == r[z]) w++; return w; }; vector w(K, 0); for (int k : ord) w[k] = wit(k); sort(ord.begin(), ord.end(), [&](int a, int b) { return w[a] < w[b]; }); // 根拠の少ない係数から落としてみる for (int k : ord) { ll keep = lam[k]; lam[k] = NEG; Row rec(m, NEG); rep(j, K) if (lam[j] > NEG) rep(z, m) rec[z] = max(rec[z], addn(lam[j], G[j][z])); if (rec != r) lam[k] = keep; } } struct MaxPlusAutoDP { int COL; vector Z, gs; // 列(接尾辞)の集合,生成元に対応する文字列 vector> mem; // 生成元と(窓上で)同じ正規化行をもつ文字列の集まり vector G; // 生成元(行) int K = 0; vector>> A; // A[c][i][j] vector u; MaxPlusAutoDP(int COL) : COL(COL) {} static vector strs(int COL, int len) { vector res{ "" }; int b = 0; rep(l, len) { int e = sz(res); for (int i = b; i < e; i++) rep(c, COL) res.push_back(res[i] + char('0' + c)); b = e; } return res; } Row row_of(const string& x) { Row r(sz(Z)); rep(j, sz(Z)) r[j] = H(x + Z[j]); return r; } // 候補行を追加(生成系の更新).冗長になった旧生成元は取り除く. void add(const string& x) { Row r = row_of(x); vector lam; if (r[0] > NEG) r = shifted(r, r[0]); // 生成元は g(ε)=0 に正規化:g(z) = naive(x+z) - naive(x) // 窓上で同じ行(定数差を除く)の生成元が既にあれば,より「飽和した」(長い)文字列を代表にする rep(i, sz(G)) if (G[i] == r) { mem[i].push_back(x); if (sz(x) > sz(gs[i]) || (sz(x) == sz(gs[i]) && H(x) > H(gs[i]))) gs[i] = x; return; } if (residuate(r, G, -1, lam)) return; G.push_back(r); gs.push_back(x); mem.push_back({ x }); for (int i = sz(G) - 2; i >= 0; i--) if (residuate(G[i], G, i, lam)) { G.erase(G.begin() + i); gs.erase(gs.begin() + i); mem.erase(mem.begin() + i); } } ll eval(const string& s) const { vector dp = u, nd(K); for (char ch : s) { int c = ch - '0'; fill(nd.begin(), nd.end(), NEG); rep(i, K) if (dp[i] > NEG) rep(j, K) nd[j] = max(nd[j], addn(dp[i], A[c][i][j])); dp.swap(nd); } ll res = NEG; rep(i, K) res = max(res, dp[i]); return res; } // LZ: 接尾辞の最大長,LX: 候補接頭辞の最大長,LV: 検証する文字列の最大長 bool build(int LZ, int LX, int LV, int Kmax) { Z = strs(COL, LZ); G.clear(); gs.clear(); mem.clear(); for (auto& x : strs(COL, LX)) add(x); // 閉包:各生成元 g と文字 c について g·c の行が生成系で表せるまで繰り返す for (;;) { bool changed = false; for (int i = 0; i < sz(G) && !changed; i++) rep(c, COL) { vector lam; string y = gs[i] + char('0' + c); if (!residuate(shifted(row_of(y), H(gs[i])), G, -1, lam)) { add(y); changed = true; break; } } if (sz(G) > Kmax) return false; if (!changed) break; } K = sz(G); A.assign(COL, vector>(K, vector(K, NEG))); // 生成元ごとに,同じ行をもつ文字列のうち遷移が最も疎になる代表を選ぶ(飽和が足りない代表は窓の端で偽の係数を生む) rep(i, K) { int bestCnt = INT_MAX; vector> bestA; vector cand = mem[i]; cand.push_back(gs[i]); sort(cand.begin(), cand.end()); cand.erase(unique(cand.begin(), cand.end()), cand.end()); for (auto& sc : cand) { vector> cur(COL); int cnt = 0; bool ok = true; rep(c, COL) { vector lam; Row t = shifted(row_of(sc + char('0' + c)), H(sc)); if (!residuate(t, G, -1, lam)) { ok = false; break; } prune(lam, t, G); cur[c] = lam; for (ll x : lam) cnt += x > NEG; } if (ok && cnt < bestCnt) { bestCnt = cnt; bestA = cur; } } if (bestCnt == INT_MAX) return false; rep(c, COL) A[c][i] = bestA[c]; } { vector lam; Row t = row_of(""); residuate(t, G, -1, lam); prune(lam, t, G); u = lam; } // 正規化により g_i(ε)=0 なので,答えは max_i dp[i] になる(v は不要) // 検証 for (auto& s : strs(COL, LV)) if (eval(s) != H(s)) return false; return true; } void print_code() const { auto f = [](ll x) { return x <= NEG ? string("NEG") : to_string(x); }; printf("constexpr int DIM = %d;\nconstexpr int COL = %d;\n", K, COL); printf("const ll matAs[COL][DIM][DIM] = {\n"); rep(c, COL) { printf("{"); rep(i, K) { printf("{"); rep(j, K) printf("%s%s", f(A[c][i][j]).c_str(), j + 1 < K ? "," : ""); printf("}%s", i + 1 < K ? "," : ""); } printf("}%s\n", c + 1 < COL ? "," : ""); } printf("};\nconst ll vecU[DIM] = {"); rep(i, K) printf("%s%s", f(u[i]).c_str(), i + 1 < K ? "," : ""); printf("};\n"); } }; void embed_coefs_maxplus(int COL, int wmax = 1, int LZmax = 6, int LV = 10, int Kmax = 40) { MaxPlusAutoDP dp(COL); for (int LZ = 1; LZ <= LZmax; LZ++) { int LX = 2 * (LZ + 1) * wmax + 1; bool ok = dp.build(LZ, LX, LV, Kmax); fprintf(stderr, "LZ=%d LX=%d K=%d %s\n", LZ, LX, dp.K, ok ? "OK" : "NG"); if (ok) { dp.print_code(); return; } } fprintf(stderr, "failed: increase LZmax/wmax or the function is not representable\n"); } // ---------- 提出側のテンプレ:embed_coefs_maxplus の出力を貼って使う ---------- template T solve(const string& s) { const long long NEG = LLONG_MIN / 4; // --- 出力を貼る --- constexpr int DIM = 3; constexpr int COL = 2; const ll matAs[COL][DIM][DIM] = { {{0,NEG,NEG},{1,NEG,1},{NEG,NEG,1}}, {{NEG,1,NEG},{NEG,2,NEG},{NEG,NEG,1}} }; const ll vecU[DIM] = { 0,NEG,NEG }; // ------------------ array dp; rep(i, DIM) dp[i] = vecU[i]; for (char ch : s) { int c = ch - '0'; array nd; nd.fill(NEG); rep(i, DIM) if (dp[i] > NEG) rep(j, DIM) if (matAs[c][i][j] > NEG) nd[j] = max(nd[j], dp[i] + matAs[c][i][j]); dp = nd; } long long res = NEG; rep(i, DIM) res = max(res, dp[i]); return res; // 最小化問題で符号を反転して学習したなら -res を返す } // ---------------------------------------------------------------------- int main(int argc, char** argv) { int COL = 2; //int wmax = NAIVE_ID == 1 ? 1 : (NAIVE_ID == 2 ? 3 : 1); int wmax = 1; // embed_coefs_maxplus(COL, wmax); return 0; int n; string s; cin >> n >> s; rep(i, sz(s)) { if (s[i] == '.') s[i] = '0'; else if (s[i] == '#') s[i] = '1'; } cout << solve(s) << endl; } /* 引数 既定値 意味 COL なし(必須) 文字種の数です。文字は '0'〜'0'+COL-1 で表し、愚直解 naive もこの文字で受け取ります。 wmax 1 1文字が答えを変えうる量(重みの絶対値)の最大の見積もりです。候補接頭辞の最大長を LX = 2*(LZ+1)*wmax+1 と決めるのに使います。小さすぎると生成元が飽和せず検証に失敗し、大きすぎると COL^LX 通りの候補で重くなります。 LZmax 6 接尾辞(列)の最大長 LZ を 1 から LZmax まで増やして試します。検証を通った最初の LZ で打ち切り、最後まで通らなければ失敗を報告します。 LV 10 検証する文字列の最大長です。長さ LV 以下の全文字列で、生成したDPと愚直解を照合します。COL^LV 通りなので、COL が大きいときは下げます。 Kmax 40 生成元の数(DPの次元)の上限です。超えたらその LZ は失敗とみなして次へ進みます。状態が非有界な関数では、これが増え続けて失敗の合図になります。 */