#ifndef HIDDEN_IN_VS // 折りたたみ用 // 警告の抑制 #define _CRT_SECURE_NO_WARNINGS // ライブラリの読み込み #include using namespace std; // 型名の短縮 using ll = long long; using ull = unsigned long long; // -2^63 ~ 2^63 = 9e18(int は -2^31 ~ 2^31 = 2e9) using pii = pair; using pll = pair; using pil = pair; using pli = pair; using vi = vector; using vvi = vector; using vvvi = vector; using vvvvi = vector; using vl = vector; using vvl = vector; using vvvl = vector; using vvvvl = vector; using vb = vector; using vvb = vector; using vvvb = vector; using vc = vector; using vvc = vector; using vvvc = vector; using vd = vector; using vvd = vector; using vvvd = vector; template using priority_queue_rev = priority_queue, greater>; using Graph = vvi; // 定数の定義 const double PI = acos(-1); int DX[4] = { 1, 0, -1, 0 }; // 4 近傍(下,右,上,左) int DY[4] = { 0, 1, 0, -1 }; int INF = 1001001001; ll INFL = 4004004003094073385LL; // (int)INFL = INF, (int)(-INFL) = -INF; // 入出力高速化 struct fast_io { fast_io() { cin.tie(nullptr); ios::sync_with_stdio(false); cout << fixed << setprecision(18); } } fastIOtmp; // 汎用マクロの定義 #define all(a) (a).begin(), (a).end() #define sz(x) ((int)(x).size()) #define lbpos(a, x) (int)distance((a).begin(), std::lower_bound(all(a), x)) #define ubpos(a, x) (int)distance((a).begin(), std::upper_bound(all(a), x)) #define Yes(b) {cout << ((b) ? "Yes\n" : "No\n");} #define rep(i, n) for(int i = 0, i##_len = int(n); i < i##_len; ++i) // 0 から n-1 まで昇順 #define repi(i, s, t) for(int i = int(s), i##_end = int(t); i <= i##_end; ++i) // s から t まで昇順 #define repir(i, s, t) for(int i = int(s), i##_end = int(t); i >= i##_end; --i) // s から t まで降順 #define repe(v, a) for(const auto& v : (a)) // a の全要素(変更不可能) #define repea(v, a) for(auto& v : (a)) // a の全要素(変更可能) #define repb(set, d) for(int set = 0, set##_ub = 1 << int(d); set < set##_ub; ++set) // d ビット全探索(昇順) #define repis(i, set) for(int i = lsb(set), bset##i = set; i < 32; bset##i -= 1 << i, i = lsb(bset##i)) // set の全要素(昇順) #define repp(a) sort(all(a)); for(bool a##_perm = true; a##_perm; a##_perm = next_permutation(all(a))) // a の順列全て(昇順) #define uniq(a) {sort(all(a)); (a).erase(unique(all(a)), (a).end());} // 重複除去 #define EXIT(a) {cout << (a) << endl; exit(0);} // 強制終了 #define inQ(x, y, u, l, d, r) ((u) <= (x) && (l) <= (y) && (x) < (d) && (y) < (r)) // 半開矩形内判定 // 汎用関数の定義 template inline ll powi(T n, int k) { ll v = 1; rep(i, k) v *= n; return v; } 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 T getb(T set, int i) { return (set >> i) & T(1); } template inline T smod(T n, T m) { n %= m; if (n < 0) n += m; return n; } // 非負mod // 演算子オーバーロード template inline istream& operator>>(istream& is, pair& p) { is >> p.first >> p.second; return is; } template inline istream& operator>>(istream& is, vector& v) { repea(x, v) is >> x; return is; } template inline vector& operator--(vector& v) { repea(x, v) --x; return v; } template inline vector& operator++(vector& v) { repea(x, v) ++x; return v; } #endif // 折りたたみ用 #if __has_include() #include using namespace atcoder; #ifdef _MSC_VER #include "localACL.hpp" #endif //using mint = modint1000000007; using mint = modint998244353; //using mint = static_modint<(ll)1e9>; //using mint = modint; // mint::set_mod(m); namespace atcoder { inline istream& operator>>(istream& is, mint& x) { ll x_; is >> x_; x = x_; return is; } inline ostream& operator<<(ostream& os, const mint& x) { os << x.val(); return os; } } using vm = vector; using vvm = vector; using vvvm = vector; using vvvvm = vector; using pim = pair; #endif #ifdef _MSC_VER // 手元環境(Visual Studio) #include "local.hpp" #else // 提出用(gcc) inline int popcount(int n) { return __builtin_popcount(n); } inline int popcount(ll n) { return __builtin_popcountll(n); } inline int lsb(int n) { return n != 0 ? __builtin_ctz(n) : 32; } inline int lsb(ll n) { return n != 0 ? __builtin_ctzll(n) : 64; } template inline int lsb(const bitset& b) { return b._Find_first(); } inline int msb(int n) { return n != 0 ? (31 - __builtin_clz(n)) : -1; } inline int msb(ll n) { return n != 0 ? (63 - __builtin_clzll(n)) : -1; } #define dump(...) #define dumpel(v) #define dump_list(v) #define dump_mat(v) #define input_from_file(f) #define output_to_file(f) #define Assert(b) { if (!(b)) { vc MLE(1<<30); EXIT(MLE.back()); } } // RE の代わりに MLE を出す #endif ll lcp_sum(const string& s) { auto sa = suffix_array(s); auto lcp = lcp_array(s, sa); ll sum = accumulate(all(lcp), 0LL); return sum; } pair naive(int n) { string res; ll sum_min = INFL; repb(set, n) { // repir(set, (1 << n) - 1, 0) { string s; repir(i, n - 1, 0) s += (getb(set, i) ? "b" : "a"); if (chmin(sum_min, lcp_sum(s))) { res = s; } } return make_pair(sum_min, res); } void zikken() { repi(n, 1, 21) { dump(n, ":", naive(n)); } exit(0); } /* 1 : ( 0,a) 2 : ( 0,ba) 0 3 : ( 1,baa) 1 4 : ( 2,abaa) 1 5 : ( 3,abbaa) 1 6 : ( 5,abbaaa) 2 += a 7 : ( 7,bbabaaa) 2 8 : ( 9,abbabaaa) 2 9 : (11,aabbabaaa) 2 10 : (13,aabbbabaaa) 2 11 : (16,aabbbabaaaa) 3 += a 12 : (19,babbbaabaaaa) 3 13 : (22,bbbabbaabaaaa) 3 14 : (25,abbbabbaabaaaa) 3 15 : (28,bbbababbaabaaaa) 3 16 : (31,abbbababbaabaaaa) 3 17 : (34,aabbbababbaabaaaa) 3 18 : (37,aaabbbababbaabaaaa) 3 19 : (40,aaabbbbababbaabaaaa) 3 20 : (44,aaabbbbababbaabaaaaa) 4 += a 21 : (48,baabbbbababbaaabaaaaa) 4 1 : (0,b) 2 : (0,ab) 3 : (1,abb) 4 : (2,babb) 5 : (3,baabb) 6 : (5,baabbb) 7 : (7,aababbb) 8 : (9,baababbb) 9 : (11,bbaababbb) 10 : (13,bbaaababbb) 11 : (16,bbaaababbbb) 12 : (19,abaaabbabbbb) 13 : (22,aaabaabbabbbb) 14 : (25,baaabaabbabbbb) 15 : (28,aaababaabbabbbb) 16 : (31,baaababaabbabbbb) 17 : (34,bbaaababaabbabbbb) 18 : (37,bbbaaababaabbabbbb) 19 : (40,bbbaaaababaabbabbbb) 20 : (44,bbbaaaababaabbabbbbb) 21 : (48,abbaaaababaabbbabbbbb) 1 : (0,b) 2 : (0,ba) 3 : (1,bba) 4 : (2,bbab) 5 : (3,bbaab) 6 : (5,bbbaab) 7 : (7,bbbabaa) 8 : (9,bbbabaab) 9 : (11,bbbabaabb) 10 : (13,bbbabaaabb) 11 : (16,bbbbabaaabb) 12 : (19,bbbbabbaaaba) 13 : (22,bbbbabbaabaaa) 14 : (25,bbbbabbaabaaab) 15 : (28,bbbbabbaababaaa) 16 : (31,bbbbabbaababaaab) 17 : (34,bbbbabbaababaaabb) 18 : (37,bbbbabbaababaaabbb) 19 : (40,bbbbabbaababaaaabbb) 20 : (44,bbbbbabbaababaaaabbb) 21 : (48,bbbbbabbbaababaaaabba) 1 : ( 0,a) 2 : ( 0,ab) 0 3 : ( 1,aab) 1 4 : ( 2,aaba) 1 5 : ( 3,aabba) 2 6 : ( 5,aaabba) 2 7 : ( 7,aaababb) 2 8 : ( 9,aaababba) 2 9 : (11,aaababbaa) 2 10 : (13,aaababbbaa) 2 11 : (16,aaaababbbaa) 3 12 : (19,aaaabaabbbab) 3 13 : (22,aaaabaabbabbb) 3 14 : (25,aaaabaabbabbba) 3 15 : (28,aaaabaabbababbb) 3 16 : (31,aaaabaabbababbba) 3 17 : (34,aaaabaabbababbbaa) 3 18 : (37,aaaabaabbababbbaaa) 3 19 : (40,aaaabaabbababbbbaaa) 3 20 : (44,aaaaabaabbababbbbaaa) 4 21 : (48,aaaaabaaabbababbbbaab) 4 */ //【ランレングス符号(文字列)】O(n) /* * 文字列 s[0..n) をランレングス符号化し,結果を格納したリスト cls を返す. * cls[i] = {c, l} は前から i 番目の連が l 個の文字 c からなることを表す. */ vector> run_length_encoding(const string& s) { // verify : https://atcoder.jp/contests/abc124/tasks/abc124_d int n = sz(s); vector> cls; if (n == 0) return cls; cls.emplace_back(s[0], 1); // いま読んでいる文字の種類を記憶する. char c = s[0]; repi(i, 1, n - 1) { // 記憶している文字と同じ文字の場合 if (s[i] == c) { // 列の長さを増やす. cls.back().second++; } // 記憶している文字と異なる文字の場合 else { // 新しい文字を記憶しておく. c = s[i]; // 新たな列を追加する. cls.emplace_back(c, 1); } } return cls; } void zikken2() { repi(n, 1, 21) { auto [sum, s] = naive(n); dump(n, ":", run_length_encoding(s)); } exit(0); } /* 1 : (a,1) 2 : (a,1) (b,1) 3 : (a,2) (b,1) 4 : (a,2) (b,1) (a,1) 5 : (a,2) (b,2) (a,1) 6 : (a,3) (b,2) (a,1) 7 : (a,3) (b,1) (a,1) (b,2) 8 : (a,3) (b,1) (a,1) (b,2) (a,1) 9 : (a,3) (b,1) (a,1) (b,2) (a,2) 10 : (a,3) (b,1) (a,1) (b,3) (a,2) 11 : (a,4) (b,1) (a,1) (b,3) (a,2) 12 : (a,4) (b,1) (a,2) (b,3) (a,1) (b,1) 13 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,3) 14 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,3) (a,1) 15 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,1) (a,1) (b,3) 16 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,1) (a,1) (b,3) (a,1) 17 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,1) (a,1) (b,3) (a,2) 18 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,1) (a,1) (b,3) (a,3) 19 : (a,4) (b,1) (a,2) (b,2) (a,1) (b,1) (a,1) (b,4) (a,3) 20 : (a,5) (b,1) (a,2) (b,2) (a,1) (b,1) (a,1) (b,4) (a,3) 21 : (a,5) (b,1) (a,3) (b,2) (a,1) (b,1) (a,1) (b,4) (a,2) (b,1) */ void zikken3() { string s = "aaaabaabbababbbaa"; auto sa = suffix_array(s); auto lcp = lcp_array(s, sa); dump(sa); dump(lcp); dump(accumulate(all(lcp), 0LL)); rep(i, sz(s)) { dump(s.substr(sa[i])); } exit(0); } vi de_bruijn(int K, int L) { vi res; res.reserve(powi(K, L)); vi seq(K * L); function rf = [&](int t, int p) { if (t > L) { if (L % p == 0) { copy(seq.begin() + 1, seq.begin() + p + 1, back_inserter(res)); } return; } seq[t] = seq[t - p]; rf(t + 1, p); repi(j, seq[t - p] + 1, K - 1) { seq[t] = j; rf(t + 1, t); } }; rf(1, 1); return res; } void zikken4() { int L = 5; auto seq = de_bruijn(2, L); dump(seq); set st; rep(i, sz(seq) - L) { vi sub(seq.begin() + i, seq.begin() + (i + L - 1)); st.insert(sub); dump(sz(st)); } exit(0); } //【ローリングハッシュ(数値文字列,加減可能)】 /* * Number_rolling_hash(string s, ull B = 10, bool reversible = false) : O(n) * B 進数値文字列 s[0..n) で初期化する.reversible = true にすると逆順のハッシュ値も計算可能になる. * * ull get(int l, int r) : O(1) * 部分数値文字列 s[l..r) のハッシュ値を返す(空なら 0) * * ull get_rev(int l, int r) : O(1) * 部分数値文字列 s[l..r) を反転した数値文字列のハッシュ値を返す(空なら 0) * * ull join(ull hs, ull ht, int len) : O(1) * ハッシュ値 hs をもつ s とハッシュ値 ht をもつ t[0..len) を連結した s+t のハッシュ値を返す. * * ull add(ull hA, ull hB) : O(1) * ハッシュ値 hA, hB が表す数値の和のハッシュ値を返す. * * ull sub(ull hA, ull hB) : O(1) * ハッシュ値 hA, hB が表す数値の差(hA 側 - hB 側)のハッシュ値を返す. */ struct Number_rolling_hash { static constexpr ull MASK30 = (1ULL << 30) - 1; static constexpr ull MASK31 = (1ULL << 31) - 1; static constexpr ull MOD = (1ULL << 61) - 1; // 法(素数) // a mod (2^61 - 1) を返す. inline ull get_mod(ull a) const { ull ah = a >> 61, al = a & MOD; ull res = ah + al; if (res >= MOD) res -= MOD; return res; } // x ≡ a b mod (2^61 - 1) なる x < 2^63 を返す(ただし a, b < 2^61) inline ull mul(ull a, ull b) const { ull ah = a >> 31, al = a & MASK31; ull bh = b >> 31, bl = b & MASK31; ull c = ah * bl + bh * al; ull ch = c >> 30, cl = c & MASK30; ull term1 = 2 * ah * bh; ull term2 = ch + (cl << 31); ull term3 = al * bl; return term1 + term2 + term3; // < 2^63 } // 列の長さ int n; // 基数 ull B; // powB[i] : B^i vector powB; // v[i] : s[0..i) のハッシュ値 Σj∈[0..i) s[j] 10^(i-1-j) // v_rev[i] : s[n-i..n) を反転した文字列のハッシュ値 vector v, v_rev; public: // 数値文字列 s[0..n) で初期化する. Number_rolling_hash(const string& s, ull B = 10, bool reversible = false) : n(sz(s)), B(B), powB(n + 1), v(n + 1) { // verify : https://codeforces.com/contest/898/problem/F powB[0] = 1; rep(i, n) powB[i + 1] = get_mod(mul(powB[i], B)); rep(i, n) v[i + 1] = get_mod(mul(v[i], B) + (ull)(s[i] - '0')); if (reversible) { v_rev.resize(n + 1); rep(i, n) v_rev[i + 1] = get_mod(mul(v_rev[i], B) + (ull)(s[n - 1 - i] - '0')); } } Number_rolling_hash() : n(0), B(1) {} // s[l..r) のハッシュ値を返す ull get(int l, int r) const { // verify : https://codeforces.com/contest/898/problem/F // chmax(l, 0); chmin(r, n); if (l >= r) return 0; return get_mod(v[r] + 4 * MOD - mul(v[l], powB[r - l])); } // s[l..r) を反転した文字列のハッシュ値を返す ull get_rev(int l, int r) { chmax(l, 0); chmin(r, n); if (l >= r) return 0; Assert(!v_rev.empty()); // s[l, r) を反転した文字列は s_rev[n-r, n-l) に等しい. return get_mod(v_rev[n - l] + 4 * MOD - mul(v_rev[n - r], powB[r - l])); } // ハッシュ値 hs をもつ s とハッシュ値 ht をもつ t[0..len) を連結した s+t のハッシュ値を返す. ull join(ull hs, ull ht, int len) const { Assert(len <= n); return get_mod(ht + mul(hs, powB[len])); } // ハッシュ値 hA, hB が表す数値の和のハッシュ値を返す. ull add(ull hA, ull hB) { // verify : https://codeforces.com/contest/898/problem/F return get_mod(hA + hB); } // ハッシュ値 hA, hB が表す数値の差のハッシュ値を返す. ull sub(ull hA, ull hB) { return get_mod(hA + MOD - hB); } }; // ハミルトンパスを抜いた後が非連結なのでオイラー閉路が存在しない. string WA(int n) { dump("n:", n); int L = 1; repi(l, 1, INF) { ll len = (1LL << l) + (l - 1); if (len > n) { L = l - 1; break; } } int pL = 1 << L; dump("L:", L); auto seq = de_bruijn(2, L); rep(i, L - 1) seq.push_back(seq[i]); string s; repe(x, seq) s += '0' + x; dump(s, sz(s)); Number_rolling_hash S(s, 2); vb used(1LL << (L + 1)); rep(i, pL - 1) used[S.get(i, i + L + 1)] = 1; function rf = [&]() { dump(s); dump(used); dump(sz(s)); if (sz(s) == n) return true; rep(b, 2) { s.push_back('0' + b); S.powB.push_back(S.get_mod(S.mul(S.powB.back(), S.B))); S.v.push_back(S.get_mod(S.mul(S.v.back(), S.B) + (ull)b)); dump("!"); auto h = S.get(sz(s) - L - 1, sz(s)); dump("!!"); //dump(s, h); dump(s); dump(S.powB); dump(S.v); if (!used[h]) { used[h] = 1; if (rf()) return true; used[h] = 0; } S.v.pop_back(); S.powB.pop_back(); s.pop_back(); } return false; }; rf(); rep(i, n) s[i] = s[i] - '0' + 'a'; return s; } //【置換 → 巡回置換の積】O(n) /* * [0..n) の置換 i → p[i] を巡回置換の積に分解し,巡回置換表記のリストを返す. */ vvi cycle_decomposition(const vi& p) { // verify : https://atcoder.jp/contests/abc175/tasks/abc175_d int n = sz(p); vvi cycles; vb seen(n); rep(i, n) { // 抽出済のサイクルに含まれるなら次へ if (seen[i]) continue; // 新しいサイクルを発見 cycles.push_back(vi()); // サイクルを順に格納していく. int s = i; do { cycles.rbegin()->push_back(s); seen[s] = true; s = p[s]; } while (s != i); } return cycles; } string solve(int n) { int L = 1; repi(l, 1, INF) { ll len = (1LL << l) + (l - 1); if (len > n) { L = l - 1; break; } } int pL = 1 << L; dump("L:", L); auto seq = de_bruijn(2, L); rep(i, L) seq.push_back(seq[i]); dump(seq); vi ec; int pt = 0; int mask = pL - 1; rep(i, L) pt = (pt << 1) ^ seq[i]; repi(i, L, pL + L - 1) { ec.push_back(pt); pt = ((pt << 1) & mask) ^ seq[i]; } ec.push_back(pt); dump(ec); vi nxt(pL, -1); rep(i, pL) nxt[ec[i]] = ec[i + 1] ^ 1; dump(nxt); auto cs = cycle_decomposition(nxt); dumpel(cs); vi id(pL, -1); rep(k, sz(cs)) id[cs[k].back()] = k; dump(id); vi res; vi sub; int len = pL + L - 1; rep(i, pL) { sub.push_back(ec[i]); if (id[ec[i]] == -1) continue; auto& c = cs[id[ec[i]]]; if (len + sz(c) < n) { repe(v, c) sub.push_back(v); len += sz(c); continue; } dump(sub); repi(i2, i + 1, pL - 1) res.push_back(ec[i2]); dump(res); repe(v, sub) res.push_back(v); dump(res); repe(v, c) { if (len == n) break; res.push_back(v); len++; } break; } dump(res); string sres; repir(i, L - 1, 1) sres += "ab"[getb(res[0], i)]; repe(v, res) sres += "ab"[v & 1]; return sres; } void Main() { int n; cin >> n; auto res = solve(n); dump(lcp_sum(res)); cout << res << endl; } int main() { // input_from_file("input.txt"); // output_to_file("output.txt"); // dump(de_bruijn(4, 2)); exit(0); // zikken2(); // zikken4(); int t = 1; cin >> t; // マルチテストケースの場合 while (t--) { dump("------------------------------"); Main(); } }