内部の問題IDがasaporo_fなのとても気になる。

solution

答えを二分探索。判定は貪欲を接尾辞配列で高速に。$O(N \log N)$。

まず、,は均等に入れる方がよい。分割後文字列の最大長は$L = \lceil \frac{|S|}{K+1} \rceil$未満にはできない。 $S$の長さ$L$の部分文字列$A$を取り、答えがこれより小さいかを貪欲で判定する。 $i$文字目まで使って、部分文字列$[i, i+L)$が$A$以下なら$i \gets i+L$、そうでなければ$i \gets i+L-1$として更新していき、$K+1$回以内に$\S|$まで見終わればよい。 部分文字列$[i, i+L)$と$A$の比較には接尾辞配列を使うと判定が$O(N)$になる。

二分探索は$N+1$個の部分文字列全ての上でやってよい。 末尾付近の長さが$L$より真に小さい部分文字列はその末尾を1で埋めたものより小さく、条件として厳しいため真にならない。 単純な比較だと$L+1$番目以降の文字列が効いてきてしまうが、そのような場合は$L$番目までの部分が一致する部分文字列$A, A’$があり、どちらか大きい方では上手く動く。

implementation

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#define repeat(i,n) for (int i = 0; (i) < (n); ++(i))
#define repeat_from(i,m,n) for (int i = (m); (i) < (n); ++(i))
using namespace std;

vector<int> suffix_array(string const & s) { // O(N (\log N)^2), sa[i] is the index of i-th smallest substring of s
    int n = s.length();
    vector<int> sa(n+1);
    vector<int> rank(n+1);
    repeat (i,n+1) {
        sa[i] = i;
        rank[i] = i < n ? s[i] : -1;
    }
    auto rankf = [&](int i) { return i <= n ? rank[i] : -1; };
    vector<int> nxt(n+1);
    for (int k = 1; k <= n; k <<= 1) {
        auto cmp = [&](int i, int j) { return make_pair(rank[i], rankf(i + k)) < make_pair(rank[j], rankf(j + k)); };
        sort(sa.begin(), sa.end(), cmp);
        nxt[sa[0]] = 0;
        repeat_from (i,1,n+1) {
            nxt[sa[i]] = nxt[sa[i-1]] + (cmp(sa[i-1], sa[i]) ? 1 : 0);
        }
        rank.swap(nxt);
    }
    return sa;
}

int main() {
    int k; string s; cin >> k >> s;
    vector<int> sa = suffix_array(s);
    vector<int> rank(sa.size()); repeat (i,sa.size()) rank[sa[i]] = i;
    int l = (s.length()+k) / (k+1);
    function<bool (int)> pred = [&](int a) { // monotonic
        int used = 0;
        for (int i = 0; i < s.length(); ++ used) {
            int delta = rank[i] < a ? l : l-1;
            if (delta == 0) return false;
            i += delta;
        }
        return used <= k+1;
    };
    int low = 0, high = rank.size();
    while (low + 1 < high) {
        int mid = (low + high) / 2;
        (pred(mid) ? high : low) = mid;
    }
    int a = low;
    cout << s.substr(sa[a], l) << endl;
    return 0;
}