mayoko’s diary

プロコンとかいろいろ。

AtCoder Regular Contest 055 C - ABCAC

解法

ABCAC の 2 つ目の A の開始位置について全探索します。この位置を rp として, 最大で A の長さをどこまで長くできるか(lenA とする)を考えます。これはローリングハッシュを使って二分探索すればできます。

また, C についても文字列 S を逆から見れば 同様にローリングハッシュの二分探索を使って C の最大の長さ(lenC とする)を求められます。
A, C の長さを a, c とすると以下の条件が成り立っている必要があります(文字列 S の長さを n とします)。

  • a+c = n-rp (rp から A, C を連結させて文字列が終わるので)
  • a+c < rp (B という文字列が存在してないといけない)
  • 1 <= a <= lenA, 1 <= c <= lenC

lenA, lenC の最大の長さを n-rp-1 とすれば A が rp からスタートする場合の ABCAC の場合の数は, max(0, lenA+lenC - (n-rp) + 1) で求められます(これ考えるのに時間かかった…イメージとしては, a 軸, c 軸の二次元平面を考えて, a+c = n-rp という直線を考えます)。

std::random_device rnd;
const ull mul[2] = {10000 + (rnd()%10000), 10000 + (rnd()%10000)};
const ull mod[2] = {1000000007, 1000000009};

class RollingHash {
public:
    RollingHash() {}
    template<class T>
    RollingHash(const T& s) {
        init(s);
    }
    template<class T>
    void init(const T& s) {
        n = s.size();
        for (int i = 0; i < 2; i++) {
            pow[i].resize(n+1);
            h[i].resize(n+1);
            pow[i][0] = 1; h[i][0] = 0;
        }
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < n; j++) {
                pow[i][j+1] = (mul[i]*pow[i][j]) % mod[i];
                h[i][j+1] = (s[j] + h[i][j]*mul[i]) % mod[i];
            }
        }
    }
    inline pair<ull, ull> hash(int i) const {return make_pair(h[0][i], h[1][i]);}
    // [l, r)
    inline pair<ull, ull> hash(int l, int r) const {
        ull p0 = (h[0][r] - (h[0][l]*pow[0][r-l]%mod[0]) + mod[0]) % mod[0];
        ull p1 = (h[1][r] - (h[1][l]*pow[1][r-l]%mod[1]) + mod[1]) % mod[1];
        return make_pair(p0, p1);
    }
private:
    int n;
    vector<ull> pow[2], h[2];
};


int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    string S;
    cin >> S;
    RollingHash rh(S);
    string T = S;
    reverse(T.begin(), T.end());
    RollingHash rht(T); 
    int n = S.size();
    ll ans = 0;
    for (int rp = 3; rp < n-1; rp++) {
        if (n-rp >= rp) continue;
        int lenA = [&]() {
            int low = 0, high = min(rp, n-rp)+1;
            while (high - low > 1) {
                const int med = (low+high)/2;
                if (rh.hash(0, med) == rh.hash(rp, rp+med)) low = med;
                else high = med;
            }
            return min(low, n-rp-1);
        }();
        int lenC = [&]() {
            int low = 0, high = min(rp, n-rp)+1;
            while (high-low > 1) {
                const int med = (low+high)/2;
                if (rht.hash(0, med) == rht.hash(n-rp, n-rp+med)) low = med;
                else high = med;
            }
            return min(low, n-rp-1);
        }();
        ans += max(0, (lenA+lenC)-(n-rp)+1);
        //cout << rp << " " << ans << endl;
    }
    cout << ans << endl;
    return 0;
}

B よりこっちのほうが発想はシンプル。ABCAC だけど ACAC の人のほうが多かった印象。