POJ 1854 Evil Straw Warts Live(BOJ 4309,UVA 10716)

http://poj.org/problem?id=1854 https://www.acmicpc.net/problem/4309 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1657

https://vjudge.net/problem/POJ-1854 https://vjudge.net/problem/Baekjoon-4309 https://vjudge.net/problem/UVA-10716

問題概要

  • 文字列$s$が与えられる。隣接swapによって回文にできるか判定、できる場合は最小回数を出力

制約

  • $|s|\leq 8000$

解法メモ

  • 回文にできる条件は「奇数個存在する値が1種類以下であること」。これはよく見る気がする

  • 最終的にどのような形になるかだが、これは左半分を貪欲に決めてよい

  • $s$の各文字$s_i$を順番に見て、登場回数が全体の半分を超えるまで取っていく(実装を参照)

  • あとは$p_i =$ 「文字$s_i$が最終的につくる回文で何番目に出てくるか」の反転数が答えになる

実装例

BITで実装したもの。最初に書いた下の雑実装マージソートより数倍速い

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
typedef long long ll;
#define rep(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define all(v) v.begin(), v.end()
using namespace std;

template <typename T>
struct BIT {
    int n;
    vector<T> data;
    BIT(int n) : n(n), data(n + 1, 0) {}
    BIT() {}
    void add(int i, T x = 1) {
        for (i++; i <= n; i += (i & -i)) data[i] += x;
    }
    T sum(int i) {
        T s(0);
        for (i++; i; i -= (i & -i)) s += data[i];
        return s;
    }
    T sum(int l, int r) { return sum(r - 1) - sum(l - 1); }
    T get(int i) { return sum(i) - sum(i - 1); }

    int lower_bound(T w) {
        if (w <= 0) {
            return 0;
        }
        int x = 0, r = 1;
        while (r < n) r <<= 1;
        for (int len = r; len > 0; len >>= 1) {
            if (x + len <= n && data[x + len] < w) {
                w -= data[x + len];
                x += len;
            }
        }
        return x;
    }
};

const vector<int> *ptr;
bool compare(int a, int b) { return (ptr->at(a) == ptr->at(b)) ? (a > b) : (ptr->at(a) > ptr->at(b)); }
template <typename T>
long long inversion(const vector<T> &v) {
    const int n = v.size();
    BIT<int> bit(n);
    vector<int> idx(n);
    for (int i = 0; i < n; ++i) idx[i] = i;
    ptr = &v;
    sort(idx.begin(), idx.end(), compare);
    long long ans = 0;
    for (int i = 0; i < n; ++i) {
        int id = idx[i];
        ans += bit.sum(0, id);
        bit.add(id, 1);
    }
    return ans;
};

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int q;
    cin >> q;
    while (q--) {
        string s;
        cin >> s;

        vector<int> cnt(26, 0);
        rep(i, s.size()) cnt[s[i] - 'a']++;

        int oddCnt = 0;
        rep(i, 26) if (cnt[i] % 2 == 1) oddCnt++;
        if (oddCnt > 1) {
            cout << "Impossible\n";
            continue;
        }

        string t;  // 最終的に作る文字列
        {
            vector<int> tmp(26);
            rep(i, s.size()) {
                // s[i]の現時点での登場回数が半分以下ならtに追加
                tmp[s[i] - 'a']++;
                if (tmp[s[i] - 'a'] <= cnt[s[i] - 'a'] / 2) t.push_back(s[i]);
            }
        }
        // 後ろ半分を繋げて回文にする
        string u = t;
        reverse(all(u));
        if (s.size() % 2 == 1) {  // 奇数の場合は中央の文字を追加
            rep(i, 26) {
                if (cnt[i] % 2 == 1) {
                    t.push_back(i + 'a');
                    break;
                }
            }
        }
        t += u;

        // sの各文字が最終的には何番目に登場するかの順列を求める
        vector<vector<int> > idx(26);
        {
            vector<int> tmp(26);
            rep(i, t.size()) {
                idx[t[i] - 'a'].push_back(i);
                tmp[t[i] - 'a']++;
            }
        }
        vector<int> p(s.size());
        {
            vector<int> tmp(26);
            rep(i, s.size()) {
                p[i] = idx[s[i] - 'a'][tmp[s[i] - 'a']];
                tmp[s[i] - 'a']++;
            }
        }

        // pの反転数が答え
        cout << inversion(p) << "\n";
    }
}

マージソートで反転数を求めている。実装が雑なのでかなり低速

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
typedef long long ll;
#define rep(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define all(v) v.begin(), v.end()
using namespace std;

ll ans = 0;

template <typename T>
void merge(vector<T> &v1, vector<T> &v2, vector<T> &v) {
    size_t i1 = 0, i2 = 0;
    while (i1 < v1.size() || i2 < v2.size()) {
        if (i2 == v2.size() || (i1 < v1.size() && (v1[i1] < v2[i2]))) {
            v.push_back(v1[i1]);
            i1++;
        } else {
            v.push_back(v2[i2]);
            i2++;
            ans += v1.size() - i1;
        }
    }
}
template <typename T>
void merge_sort(vector<T> &v) {
    if (v.size() <= 1) return;
    vector<T> u(v.begin(), v.begin() + v.size() / 2);
    vector<T> w(v.begin() + v.size() / 2, v.end());
    merge_sort(u);
    merge_sort(w);
    v.clear();
    merge(u, w, v);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int q;
    cin >> q;
    while (q--) {
        string s;
        cin >> s;

        vector<int> cnt(26, 0);
        rep(i, s.size()) cnt[s[i] - 'a']++;

        int oddCnt = 0;
        rep(i, 26) if (cnt[i] % 2 == 1) oddCnt++;
        if (oddCnt > 1) {
            cout << "Impossible\n";
            continue;
        }

        string t;  // 最終的に作る文字列
        {
            vector<int> tmp(26);
            rep(i, s.size()) {
                // s[i]の現時点での登場回数が半分以下ならtに追加
                tmp[s[i] - 'a']++;
                if (tmp[s[i] - 'a'] <= cnt[s[i] - 'a'] / 2) t.push_back(s[i]);
            }
        }
        // 後ろ半分を繋げて回文にする
        string u = t;
        reverse(all(u));
        if (s.size() % 2 == 1) {  // 奇数の場合は中央の文字を追加
            rep(i, 26) {
                if (cnt[i] % 2 == 1) {
                    t.push_back(i + 'a');
                    break;
                }
            }
        }
        t += u;

        // sの各文字が最終的には何番目に登場するかの順列を求める
        vector<vector<int> > idx(26);
        {
            vector<int> tmp(26);
            rep(i, t.size()) {
                idx[t[i] - 'a'].push_back(i);
                tmp[t[i] - 'a']++;
            }
        }
        vector<int> p(s.size());
        {
            vector<int> tmp(26);
            rep(i, s.size()) {
                p[i] = idx[s[i] - 'a'][tmp[s[i] - 'a']];
                tmp[s[i] - 'a']++;
            }
        }

        // pの反転数が答え
        ans = 0;
        merge_sort(p);
        cout << ans << "\n";
    }
}