#include #include #include namespace suisen { // ! utility template using constraints_t = std::enable_if_t, std::nullptr_t>; template constexpr decltype(auto) constexpr_if(Then&& then, OrElse&& or_else) { if constexpr (cond_v) { return std::forward(then); } else { return std::forward(or_else); } } // ! function template using is_same_as_invoke_result = std::is_same, ReturnType>; template using is_uni_op = is_same_as_invoke_result; template using is_bin_op = is_same_as_invoke_result; template using is_comparator = std::is_same, bool>; // ! integral template >> constexpr int bit_num = std::numeric_limits>::digits; template struct is_nbit { static constexpr bool value = bit_num == n; }; template static constexpr bool is_nbit_v = is_nbit::value; // ? template struct safely_multipliable {}; template <> struct safely_multipliable { using type = long long; }; template <> struct safely_multipliable { using type = __int128_t; }; template <> struct safely_multipliable { using type = unsigned long long; }; template <> struct safely_multipliable { using type = __uint128_t; }; template <> struct safely_multipliable { using type = __uint128_t; }; template <> struct safely_multipliable { using type = float; }; template <> struct safely_multipliable { using type = double; }; template <> struct safely_multipliable { using type = long double; }; template using safely_multipliable_t = typename safely_multipliable::type; } // namespace suisen // ! type aliases using i128 = __int128_t; using u128 = __uint128_t; template using pq_greater = std::priority_queue, std::greater>; template using umap = std::unordered_map; // ! macros (capital: internal macro) #define OVERLOAD2(_1,_2,name,...) name #define OVERLOAD3(_1,_2,_3,name,...) name #define OVERLOAD4(_1,_2,_3,_4,name,...) name #define REP4(i,l,r,s) for(std::remove_reference_t>i=(l);i<(r);i+=(s)) #define REP3(i,l,r) REP4(i,l,r,1) #define REP2(i,n) REP3(i,0,n) #define REPINF3(i,l,s) for(std::remove_reference_t>i=(l);;i+=(s)) #define REPINF2(i,l) REPINF3(i,l,1) #define REPINF1(i) REPINF2(i,0) #define RREP4(i,l,r,s) for(std::remove_reference_t>i=(l)+fld((r)-(l)-1,s)*(s);i>=(l);i-=(s)) #define RREP3(i,l,r) RREP4(i,l,r,1) #define RREP2(i,n) RREP3(i,0,n) #define rep(...) OVERLOAD4(__VA_ARGS__, REP4 , REP3 , REP2 )(__VA_ARGS__) #define rrep(...) OVERLOAD4(__VA_ARGS__, RREP4 , RREP3 , RREP2 )(__VA_ARGS__) #define repinf(...) OVERLOAD3(__VA_ARGS__, REPINF3, REPINF2, REPINF1)(__VA_ARGS__) #define CAT_I(a, b) a##b #define CAT(a, b) CAT_I(a, b) #define UNIQVAR(tag) CAT(tag, __LINE__) #define loop(n) for (std::remove_reference_t> UNIQVAR(loop_variable) = n; UNIQVAR(loop_variable) --> 0;) #define all(iterable) std::begin(iterable), std::end(iterable) #define input(type, ...) type __VA_ARGS__; read(__VA_ARGS__) #ifdef LOCAL # define debug(...) debug_internal(#__VA_ARGS__, __VA_ARGS__) template void debug_internal(const char* s, T&& first, Args&&... args) { constexpr const char* prefix = "[\033[32mDEBUG\033[m] "; constexpr const char* open_brakets = sizeof...(args) == 0 ? "" : "("; constexpr const char* close_brakets = sizeof...(args) == 0 ? "" : ")"; std::cerr << prefix << open_brakets << s << close_brakets << ": " << open_brakets << std::forward(first); ((std::cerr << ", " << std::forward(args)), ...); std::cerr << close_brakets << "\n"; } #else # define debug(...) void(0) #endif // ! I/O utilities // pair template std::ostream& operator<<(std::ostream& out, const std::pair &a) { return out << a.first << ' ' << a.second; } // tuple template std::ostream& operator<<(std::ostream& out, const std::tuple &a) { if constexpr (N >= std::tuple_size_v>) { return out; } else { out << std::get(a); if constexpr (N + 1 < std::tuple_size_v>) { out << ' '; } return operator<<(out, a); } } // vector template std::ostream& operator<<(std::ostream& out, const std::vector &a) { for (auto it = a.begin(); it != a.end();) { out << *it; if (++it != a.end()) out << ' '; } return out; } // array template std::ostream& operator<<(std::ostream& out, const std::array &a) { for (auto it = a.begin(); it != a.end();) { out << *it; if (++it != a.end()) out << ' '; } return out; } inline void print() { std::cout << '\n'; } template inline void print(const Head &head, const Tail &...tails) { std::cout << head; if (sizeof...(tails)) std::cout << ' '; print(tails...); } template auto print_all(const Iterable& v, std::string sep = " ", std::string end = "\n") -> decltype(std::cout << *v.begin(), void()) { for (auto it = v.begin(); it != v.end();) { std::cout << *it; if (++it != v.end()) std::cout << sep; } std::cout << end; } // pair template std::istream& operator>>(std::istream& in, std::pair &a) { return in >> a.first >> a.second; } // tuple template std::istream& operator>>(std::istream& in, std::tuple &a) { if constexpr (N >= std::tuple_size_v>) { return in; } else { return operator>>(in >> std::get(a), a); } } // vector template std::istream& operator>>(std::istream& in, std::vector &a) { for (auto it = a.begin(); it != a.end(); ++it) in >> *it; return in; } // array template std::istream& operator>>(std::istream& in, std::array &a) { for (auto it = a.begin(); it != a.end(); ++it) in >> *it; return in; } template void read(Args &...args) { ( std::cin >> ... >> args ); } // ! integral utilities // Returns pow(-1, n) template constexpr inline int pow_m1(T n) { return -(n & 1) | 1; } // Returns pow(-1, n) template <> constexpr inline int pow_m1(bool n) { return -int(n) | 1; } // Returns floor(x / y) template constexpr inline T fld(const T x, const T y) { return (x ^ y) >= 0 ? x / y : (x - (y + pow_m1(y >= 0))) / y; } template constexpr inline T cld(const T x, const T y) { return (x ^ y) <= 0 ? x / y : (x + (y + pow_m1(y >= 0))) / y; } template > = nullptr> constexpr inline int popcount(const T x) { return __builtin_popcount(x); } template > = nullptr> constexpr inline int popcount(const T x) { return __builtin_popcount(x); } template > = nullptr> constexpr inline int popcount(const T x) { return __builtin_popcountll(x); } template > = nullptr> constexpr inline int count_lz(const T x) { return x ? __builtin_clz(x) : suisen::bit_num; } template > = nullptr> constexpr inline int count_lz(const T x) { return x ? __builtin_clz(x) : suisen::bit_num; } template > = nullptr> constexpr inline int count_lz(const T x) { return x ? __builtin_clzll(x) : suisen::bit_num; } template > = nullptr> constexpr inline int count_tz(const T x) { return x ? __builtin_ctz(x) : suisen::bit_num; } template > = nullptr> constexpr inline int count_tz(const T x) { return x ? __builtin_ctz(x) : suisen::bit_num; } template > = nullptr> constexpr inline int count_tz(const T x) { return x ? __builtin_ctzll(x) : suisen::bit_num; } template constexpr inline int floor_log2(const T x) { return suisen::bit_num - 1 - count_lz(x); } template constexpr inline int ceil_log2(const T x) { return floor_log2(x) + ((x & -x) != x); } template constexpr inline int kth_bit(const T x, const unsigned int k) { return (x >> k) & 1; } template constexpr inline int parity(const T x) { return popcount(x) & 1; } struct all_subset { struct all_subset_iter { const int s; int t; constexpr all_subset_iter(int s) : s(s), t(s + 1) {} constexpr auto operator*() const { return t; } constexpr auto operator++() {} constexpr auto operator!=(std::nullptr_t) { return t ? (--t &= s, true) : false; } }; int s; constexpr all_subset(int s) : s(s) {} constexpr auto begin() { return all_subset_iter(s); } constexpr auto end() { return nullptr; } }; // ! container template > = nullptr> auto priqueue_comp(const Comparator comparator) { return std::priority_queue, Comparator>(comparator); } template auto isize(const Iterable &iterable) -> decltype(int(iterable.size())) { return iterable.size(); } template > = nullptr> auto generate_vector(int n, Gen generator) { std::vector v(n); for (int i = 0; i < n; ++i) v[i] = generator(i); return v; } template auto generate_range_vector(T l, T r) { return generate_vector(r - l, [l](int i) { return l + i; }); } template auto generate_range_vector(T n) { return generate_range_vector(0, n); } template void sort_unique_erase(std::vector &a) { std::sort(a.begin(), a.end()); a.erase(std::unique(a.begin(), a.end()), a.end()); } template auto foreach_adjacent_values(InputIterator first, InputIterator last, BiConsumer f) -> decltype(f(*first++, *last), void()) { if (first != last) for (auto itr = first, itl = itr++; itr != last; itl = itr++) f(*itl, *itr); } template auto foreach_adjacent_values(Container c, BiConsumer f) -> decltype(c.begin(), c.end(), void()){ foreach_adjacent_values(c.begin(), c.end(), f); } // ! other utilities // x <- min(x, y). returns true iff `x` has chenged. template inline bool chmin(T &x, const T &y) { if (y >= x) return false; x = y; return true; } // x <- max(x, y). returns true iff `x` has chenged. template inline bool chmax(T &x, const T &y) { if (y <= x) return false; x = y; return true; } namespace suisen {} using namespace suisen; using namespace std; struct io_setup { io_setup(int precision = 20) { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout << std::fixed << std::setprecision(precision); } } io_setup_ {}; // ! code from here #include #include #include #include #include #include namespace suisen { template struct ObjectPool { using value_type = T; using value_pointer_type = T*; template using container_type = std::conditional_t, std::vector>; container_type pool; container_type stock; decltype(stock.begin()) it; ObjectPool() : ObjectPool(0) {} ObjectPool(int siz) : pool(siz), stock(siz) { clear(); } int capacity() const { return pool.size(); } int size() const { return it - stock.begin(); } value_pointer_type alloc() { if constexpr (auto_extend) ensure(); return *it++; } void free(value_pointer_type t) { *--it = t; } void clear() { int siz = pool.size(); it = stock.begin(); for (int i = 0; i < siz; i++) stock[i] = &pool[i]; } void ensure() { if (it != stock.end()) return; int siz = stock.size(); for (int i = siz; i <= siz * 2; ++i) { stock.push_back(&pool.emplace_back()); } it = stock.begin() + siz; } }; } // namespace suisen namespace suisen::bbst::internal { template struct RedBlackTreeNodeBase { enum RedBlackTreeNodeColor { RED, BLACK }; using base_type = void; using size_type = int; using value_type = T; using node_type = Derived; using tree_type = node_type*; using color_type = RedBlackTreeNodeColor; RedBlackTreeNodeBase() = default; static inline ObjectPool pool{}; static void init_pool(int siz) { pool = ObjectPool(siz); } static int node_num() { return pool.size(); } static tree_type empty_tree() { return nullptr; } static size_type size(tree_type node) { return node ? node->_siz : 0; } static bool empty(tree_type node) { return not node; } template static tree_type merge(tree_type l, tree_type r) { if (not l) return r; if (not r) return l; tree_type res = nullptr; if (size_type hl = height(l), hr = height(r); hl > hr) { l = node_type::push(l); tree_type c = l->_ch[1] = merge(l->_ch[1], r); if (l->_col == BLACK and c->_col == RED and color(c->_ch[1]) == RED) { std::swap(l->_col, c->_col); if (std::exchange(l->_ch[0]->_col, BLACK) == BLACK) return rotate(l, 1); } res = node_type::update(l); } else if (hr > hl) { r = node_type::push(r); tree_type c = r->_ch[0] = merge(l, r->_ch[0]); if (r->_col == BLACK and c->_col == RED and color(c->_ch[0]) == RED) { std::swap(r->_col, c->_col); if (std::exchange(r->_ch[1]->_col, BLACK) == BLACK) return rotate(r, 0); } res = node_type::update(r); } else { res = create_branch(l, r); } if constexpr (force_black_root) res->_col = BLACK; return res; } static std::pair split(tree_type node, size_type k) { if (not node) return { nullptr, nullptr }; node = node_type::push(node); if (k == 0) return { nullptr, node }; if (k == size(node)) return { node, nullptr }; tree_type l = std::exchange(node->_ch[0], nullptr); tree_type r = std::exchange(node->_ch[1], nullptr); free_node(node); if (color(l) == RED) l->_col = BLACK; if (color(r) == RED) r->_col = BLACK; size_type szl = size(l); tree_type m; if (k < szl) { std::tie(l, m) = split(l, k); return { l, merge(m, r) }; } if (k > szl) { std::tie(m, r) = split(r, k - szl); return { merge(l, m), r }; } return { l, r }; } static std::tuple split_range(tree_type node, size_type l, size_type r) { auto [tlm, tr] = split(node, r); auto [tl, tm] = split(tlm, l); return { tl, tm, tr }; } static tree_type insert(tree_type node, size_type k, const value_type& val) { auto [tl, tr] = split(node, k); return merge(merge(tl, create_leaf(val)), tr); } static tree_type push_front(tree_type node, const value_type &val) { return insert(node, 0, val); } static tree_type push_back(tree_type node, const value_type &val) { return insert(node, size(node), val); } static std::pair erase(tree_type node, size_type k) { auto [tl, tm, tr] = split_range(node, k, k + 1); value_type erased_value = tm->_val; free_node(tm); return { merge(tl, tr) , erased_value }; } static std::pair pop_front(tree_type node) { return erase(node, 0); } static std::pair pop_back(tree_type node) { return erase(node, size(node) - 1); } template static tree_type build(const std::vector& a, int l, int r) { if (r - l == 1) return create_leaf(a[l]); int m = (l + r) >> 1; return merge(build(a, l, m), build(a, m, r)); } template static tree_type build(const std::vector& a) { return a.empty() ? empty_tree() : build(a, 0, a.size()); } template static void dump(tree_type node, OutputIterator it) { if (empty(node)) return; auto dfs = [&](auto dfs, tree_type cur) -> void { if (cur->is_leaf()) { *it++ = cur->_val; return; } dfs(dfs, cur->_ch[0]); dfs(dfs, cur->_ch[1]); }; dfs(dfs, node); } // Don't use on persistent tree. static void free(tree_type node) { auto dfs = [&](auto dfs, tree_type cur) -> void { if (not cur) return; dfs(dfs, cur->_ch[0]); dfs(dfs, cur->_ch[1]); free_node(cur); }; dfs(dfs, node); } template static std::string to_string(tree_type node, ToStr f) { std::vector dat; node_type::dump(node, std::back_inserter(dat)); std::ostringstream res; int siz = dat.size(); res << '['; for (int i = 0; i < siz; ++i) { res << f(dat[i]); if (i != siz - 1) res << ", "; } res << ']'; return res.str(); } static std::string to_string(tree_type node) { return to_string(node, [](const auto &e) { return e; }); } static void check_rbtree_properties(tree_type node) { assert(color(node) == BLACK); auto dfs = [&](auto dfs, tree_type cur) -> int { if (not cur) return 0; if (cur->_col == RED) { assert(color(cur->_ch[0]) == BLACK); assert(color(cur->_ch[1]) == BLACK); } int bl = dfs(dfs, cur->_ch[0]); int br = dfs(dfs, cur->_ch[1]); assert(bl == br); return bl + (cur->_col == BLACK); }; dfs(dfs, node); } protected: color_type _col; tree_type _ch[2]{ nullptr, nullptr }; value_type _val; size_type _siz, _lev; RedBlackTreeNodeBase(const value_type& val) : _col(BLACK), _val(val), _siz(1), _lev(0) {} RedBlackTreeNodeBase(tree_type l, tree_type r) : _col(RED), _ch{ l, r }, _siz(l->_siz + r->_siz), _lev(l->_lev + (l->_col == BLACK)) {} static void clear_pool() { pool.clear(); } static int pool_capacity() { return pool.capacity(); } static color_type color(tree_type node) { return node ? node->_col : BLACK; } static size_type height(tree_type node) { return node ? node->_lev : 0; } bool is_leaf() const { return not (_ch[0] or _ch[1]); } static tree_type clone(tree_type node) { return node; } static tree_type update(tree_type node) { node->_siz = node->is_leaf() ? 1 : size(node->_ch[0]) + size(node->_ch[1]); node->_lev = node->_ch[0] ? height(node->_ch[0]) + (node->_ch[0]->_col == BLACK) : 0; return node; } static tree_type push(tree_type node) { return node; } static tree_type rotate(tree_type node, int index) { node = node_type::push(node); tree_type ch_node = node_type::push(node->_ch[index]); node->_ch[index] = std::exchange(ch_node->_ch[index ^ 1], node); return node_type::update(node), node_type::update(ch_node); } static tree_type create_leaf(const value_type& val = value_type{}) { return &(*pool.alloc() = node_type(val)); } static tree_type create_branch(tree_type l, tree_type r) { return node_type::update(&(*pool.alloc() = node_type(l, r))); } static void free_node(tree_type node) { if (node) pool.free(node); } }; } // namespace suisen namespace suisen::bbst { template typename BaseNode = internal::RedBlackTreeNodeBase> struct RedBlackTreeNode : public BaseNode> { using base_type = BaseNode>; using node_type = typename base_type::node_type; using tree_type = typename base_type::tree_type; using size_type = typename base_type::size_type; using value_type = typename base_type::value_type; friend base_type; friend typename base_type::base_type; RedBlackTreeNode() = default; private: RedBlackTreeNode(const value_type& val) : base_type(val) {} RedBlackTreeNode(tree_type l, tree_type r) : base_type(l, r) {} }; } using Node = bbst::RedBlackTreeNode; using Tree = Node::tree_type; constexpr int A = 'A' - 'A'; constexpr int G = 'G' - 'A'; constexpr int C = 'C' - 'A'; constexpr int T = 'T' - 'A'; int main() { Node::init_pool(6000000); input(int, n); vector s(n); read(s); auto seq = Node::build(s); array cnt{}; for (char c : s) { ++cnt[c - 'A']; } int offset = 0; auto get_index = [&](int ch) { int x = (ch - offset) % 26; if (x < 0) x += 26; return x; }; int ans = 0; for (;; ++ans) { int num = cnt[get_index(A)] + cnt[get_index(G)] + cnt[get_index(C)] + cnt[get_index(T)]; if (num == 0) break; char c; tie(seq, c) = Node::erase(seq, num - 1); int ch = (offset + c - 'A') % 26; int c2 = --cnt[get_index(ch)]; offset += c2; } print(ans); return 0; }