競技プログラミング用 C++ ライブラリ
string/concatenation_order.hpp · GitHub で見る
共通ページ
文字列を並べ替えて結合したときに、辞書順最小・最大となる順序や文字列を求めます。
#include "lib/string/concatenation_order.hpp"
std::vector<std::string> s{"b", "ba", "baa"};
int c = concatenation_compare("b", "ba"); // 1
auto p = concatenation_order(s); // {2,1,0}
auto q = concatenation_order(s, true); // {0,1,2}
auto small = min_concatenation(s); // "baabab"
auto large = max_concatenation(s); // "bbabaa"
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
concatenation_compare(a,b) |
a+b と b+a の辞書順比較。小さい・等しい・大きいとき、それぞれ -1・0・1 |
\(O(\lvert a\rvert+\lvert b\rvert)\) |
concatenation_order(s,descending = false) |
結合結果が辞書順最小となる元の添字の順列。true なら最大 |
\(O(N+L\log N)\) |
min_concatenation(s) |
辞書順最小の結合結果 | \(O(N+L\log N)\) |
max_concatenation(s) |
辞書順最大の結合結果 | \(O(N+L\log N)\) |
s には vector<string>・vector<string_view>・配列・span などを渡せます。入力は変更しません。空文字列は順列の先頭に元の順序でまとめます。空でない文字列のうち a+b == b+a となるものも、元の順序を保ちます。空の入力に対しては空の順列・文字列を返します。
concatenation_compare は string_view を受け取ります。一方が空なら 0 を返すため、空文字列を含む列のソートには concatenation_order を使ってください。
NUL を含む全 256 種類のバイトに対応し、バイトを符号なしとして比較します。文字列数と各文字列の長さはそれぞれ INT_MAX 以下としてください。
文字列数を \(N\)、長さの合計を \(L\) とすると、並べ替え・結合は時間 \(O(N+L\log N)\)、追加空間 \(O(N+L)\) です。\(N\le1\) の結合は時間 \(O(N+L)\) です。2 文字列の比較だけなら追加空間は \(O(1)\) です。
参考:AtCoder Algorithm Lectures:文字列の並べ替えと辞書順最小化
#ifndef CONCATENATION_ORDER_HPP
#define CONCATENATION_ORDER_HPP
#include "../components/sequence_compare.hpp"
#include "../components/z_algorithm.hpp"
#include <algorithm>
#include <cassert>
#include <concepts>
#include <cstdint>
#include <limits>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
inline int concatenation_compare(std::string_view a, std::string_view b) {
if (a.empty() || b.empty()) return 0;
bool reverse = a.size() > b.size();
if (reverse) std::swap(a, b);
std::size_t n = a.size(), m = b.size();
int value = std::char_traits<char>::compare(a.data(), b.data(), n);
if (!value && n < m) {
value = std::char_traits<char>::compare(b.data(), b.data() + n, m - n);
if (!value) value = std::char_traits<char>::compare(b.data() + m - n, a.data(), n);
}
int result = (value > 0) - (value < 0);
return reverse ? -result : result;
}
namespace concatenation_order_detail {
template <class Range>
concept Strings = std::ranges::forward_range<const Range> &&
std::is_lvalue_reference_v<std::ranges::range_reference_t<const Range>> &&
std::convertible_to<std::ranges::range_reference_t<const Range>, std::string_view>;
struct Entry {
std::string_view text;
std::vector<int> z;
std::uint64_t compared = 0;
};
template <Strings Range>
std::vector<Entry> prepare(const Range &strings) {
std::vector<Entry> entries;
[[maybe_unused]] constexpr auto maximum = std::numeric_limits<int>::max();
if constexpr (std::ranges::sized_range<const Range>) {
assert(std::ranges::size(strings) <= std::size_t(maximum));
entries.reserve(std::ranges::size(strings));
}
for (const auto &s : strings) {
std::string_view text = s;
assert(entries.size() < std::size_t(maximum) && text.size() <= std::size_t(maximum));
entries.push_back({text, {}});
}
return entries;
}
inline int compare(Entry &first, Entry &second) {
Entry *a = &first, *b = &second;
bool reverse = a->text.size() > b->text.size();
if (reverse) std::swap(a, b);
int n = int(a->text.size()), m = int(b->text.size());
int value = std::char_traits<char>::compare(a->text.data(), b->text.data(), n);
if (!value && n < m) {
if (m - n <= n) value = std::char_traits<char>::compare(b->text.data(), b->text.data() + n, m - n);
else {
int common;
if (b->z.empty() && b->compared < 8 * std::uint64_t(m)) {
common = sequence_compare_detail::prefix(b->text.data(), b->text.data() + n, m - n);
b->compared += common + 1;
} else {
if (b->z.empty()) {
b->z.resize(m);
auto s = b->text;
sequence_match_detail::z_table(m, b->z.data(), [&](int i, int j, int limit) {
return sequence_compare_detail::prefix(s.data() + i, s.data() + j, limit);
});
}
common = b->z[n];
}
if (common < m - n) value = int(static_cast<unsigned char>(b->text[common])) - int(static_cast<unsigned char>(b->text[n + common]));
}
if (!value) value = std::char_traits<char>::compare(b->text.data() + m - n, a->text.data(), n);
}
int result = (value > 0) - (value < 0);
return reverse ? -result : result;
}
inline std::vector<int> order(std::vector<Entry> &entries, bool descending) {
std::vector<int> result;
result.reserve(entries.size());
for (int i = 0; i < int(entries.size()); ++i) if (entries[i].text.empty()) result.push_back(i);
std::size_t empty = result.size();
for (int i = 0; i < int(entries.size()); ++i) if (!entries[i].text.empty()) result.push_back(i);
std::size_t n = result.size() - empty;
if (n <= 1) return result;
std::vector<int> buffer(n);
int *source = result.data() + empty, *target = buffer.data();
for (std::size_t width = 1; width < n; width *= 2) {
for (std::size_t left = 0; left < n; left += 2 * width) {
std::size_t middle = std::min(left + width, n), right = std::min(left + 2 * width, n);
if (middle < right) {
int value = compare(entries[source[middle - 1]], entries[source[middle]]);
if (descending ? value >= 0 : value <= 0) {
std::copy(source + left, source + right, target + left);
continue;
}
}
std::size_t i = left, j = middle, k = left;
while (i < middle && j < right) {
int value = compare(entries[source[i]], entries[source[j]]);
bool take_right = descending ? value < 0 : value > 0;
target[k++] = take_right ? source[j++] : source[i++];
}
while (i < middle) target[k++] = source[i++];
while (j < right) target[k++] = source[j++];
}
std::swap(source, target);
}
if (source != result.data() + empty) std::copy_n(source, n, result.data() + empty);
return result;
}
template <Strings Range>
std::string concatenate(const Range &strings, bool descending) {
auto entries = prepare(strings);
auto indices = order(entries, descending);
std::string result;
std::size_t length = 0;
for (const auto &entry : entries) {
if (entry.text.size() > result.max_size() - length) throw std::length_error("concatenation");
length += entry.text.size();
}
result.reserve(length);
for (int i : indices) if (!entries[i].text.empty()) result.append(entries[i].text);
return result;
}
}
template <concatenation_order_detail::Strings Range>
std::vector<int> concatenation_order(const Range &strings, bool descending = false) {
auto entries = concatenation_order_detail::prepare(strings);
return concatenation_order_detail::order(entries, descending);
}
template <concatenation_order_detail::Strings Range>
std::string min_concatenation(const Range &strings) {
return concatenation_order_detail::concatenate(strings, false);
}
template <concatenation_order_detail::Strings Range>
std::string max_concatenation(const Range &strings) {
return concatenation_order_detail::concatenate(strings, true);
}
#endif
#line 1 "string/concatenation_order.hpp"
#line 1 "components/sequence_compare.hpp"
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
#if defined(__x86_64__)
#include <immintrin.h>
#endif
namespace sequence_compare_detail {
#if defined(__x86_64__)
__attribute__((target("avx2"))) inline std::size_t forward_avx2(const unsigned char *a, const unsigned char *b, std::size_t size) {
std::size_t i = 0;
for (; size - i >= 32; i += 32) {
auto x = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(a + i));
auto y = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(b + i));
auto difference = ~std::uint32_t(_mm256_movemask_epi8(_mm256_cmpeq_epi8(x, y)));
if (difference) return i + std::countr_zero(difference);
}
while (i < size && a[i] == b[i]) ++i;
return i;
}
__attribute__((target("avx2"))) inline std::size_t backward_avx2(const unsigned char *a, const unsigned char *b, std::size_t size) {
std::size_t i = 0;
for (; size - i >= 32; i += 32) {
auto x = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(a - i - 32));
auto y = _mm256_loadu_si256(reinterpret_cast<const __m256i *>(b - i - 32));
auto difference = ~std::uint32_t(_mm256_movemask_epi8(_mm256_cmpeq_epi8(x, y)));
if (difference) return i + std::countl_zero(difference);
}
while (i < size && a[-std::ptrdiff_t(i) - 1] == b[-std::ptrdiff_t(i) - 1]) ++i;
return i;
}
#endif
template<bool Vectorize = true, class T>
inline int prefix(const T *first, const T *second, int limit) {
if (!limit) return 0;
int matched = 0;
if constexpr (std::is_integral_v<T> && std::has_unique_object_representations_v<T>) {
const auto *a = reinterpret_cast<const unsigned char *>(first);
const auto *b = reinterpret_cast<const unsigned char *>(second);
std::size_t offset = 0, size = std::size_t(limit) * sizeof(T);
while (size - offset >= 8) {
std::uint64_t x, y;
std::memcpy(&x, a + offset, 8);
std::memcpy(&y, b + offset, 8);
auto difference = x ^ y;
if (difference) {
int bits = std::endian::native == std::endian::little ? std::countr_zero(difference) : std::countl_zero(difference);
return static_cast<int>((offset + bits / 8) / sizeof(T));
}
offset += 8;
#if defined(__x86_64__)
if constexpr (Vectorize) if (size - offset >= 64 && __builtin_cpu_supports("avx2"))
return static_cast<int>((offset + forward_avx2(a + offset, b + offset, size - offset)) / sizeof(T));
#endif
}
matched = static_cast<int>(offset / sizeof(T));
}
while (matched < limit && first[matched] == second[matched]) ++matched;
return matched;
}
template<bool Vectorize = true, class T>
inline int suffix(const T *first_end, const T *second_end, int limit) {
if (!limit) return 0;
int matched = 0;
if constexpr (std::is_integral_v<T> && std::has_unique_object_representations_v<T>) {
const auto *a = reinterpret_cast<const unsigned char *>(first_end);
const auto *b = reinterpret_cast<const unsigned char *>(second_end);
std::size_t offset = 0, size = std::size_t(limit) * sizeof(T);
while (size - offset >= 8) {
std::uint64_t x, y;
std::memcpy(&x, a - offset - 8, 8);
std::memcpy(&y, b - offset - 8, 8);
auto difference = x ^ y;
if (difference) {
int bits = std::endian::native == std::endian::little ? std::countl_zero(difference) : std::countr_zero(difference);
return static_cast<int>((offset + bits / 8) / sizeof(T));
}
offset += 8;
#if defined(__x86_64__)
if constexpr (Vectorize) if (size - offset >= 64 && __builtin_cpu_supports("avx2"))
return static_cast<int>((offset + backward_avx2(a - offset, b - offset, size - offset)) / sizeof(T));
#endif
}
matched = static_cast<int>(offset / sizeof(T));
}
while (matched < limit && first_end[-matched - 1] == second_end[-matched - 1]) ++matched;
return matched;
}
}
#line 1 "components/z_algorithm.hpp"
namespace sequence_match_detail {
template <class F>
void z_table(int n, int *z, F match) {
if (!n) return;
z[0] = n;
for (int i = 1, base = 0, end = 0; i < n; ++i) {
int length = 0;
if (i < end) {
length = z[i - base];
if (length < end - i) {
z[i] = length;
continue;
}
length = end - i;
}
length += match(length, i + length, n - i - length);
z[i] = length;
if (i + length > end) base = i, end = i + length;
}
}
template <class F, class Emit>
void prefix_matches(int n, int m, const int *z, F match, Emit emit) {
for (int i = 0, base = 0, end = 0; i < n; ++i) {
int length = 0;
if (i < end) {
length = z[i - base];
if (length < end - i) {
emit(i, length);
continue;
}
length = end - i;
}
length += match(length, i + length, m - length);
if (i + length > end) base = i, end = i + length;
emit(i, length);
}
}
}
#line 6 "string/concatenation_order.hpp"
#include <algorithm>
#include <cassert>
#include <concepts>
#line 10 "string/concatenation_order.hpp"
#include <limits>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
#line 16 "string/concatenation_order.hpp"
#include <utility>
#include <vector>
inline int concatenation_compare(std::string_view a, std::string_view b) {
if (a.empty() || b.empty()) return 0;
bool reverse = a.size() > b.size();
if (reverse) std::swap(a, b);
std::size_t n = a.size(), m = b.size();
int value = std::char_traits<char>::compare(a.data(), b.data(), n);
if (!value && n < m) {
value = std::char_traits<char>::compare(b.data(), b.data() + n, m - n);
if (!value) value = std::char_traits<char>::compare(b.data() + m - n, a.data(), n);
}
int result = (value > 0) - (value < 0);
return reverse ? -result : result;
}
namespace concatenation_order_detail {
template <class Range>
concept Strings = std::ranges::forward_range<const Range> &&
std::is_lvalue_reference_v<std::ranges::range_reference_t<const Range>> &&
std::convertible_to<std::ranges::range_reference_t<const Range>, std::string_view>;
struct Entry {
std::string_view text;
std::vector<int> z;
std::uint64_t compared = 0;
};
template <Strings Range>
std::vector<Entry> prepare(const Range &strings) {
std::vector<Entry> entries;
[[maybe_unused]] constexpr auto maximum = std::numeric_limits<int>::max();
if constexpr (std::ranges::sized_range<const Range>) {
assert(std::ranges::size(strings) <= std::size_t(maximum));
entries.reserve(std::ranges::size(strings));
}
for (const auto &s : strings) {
std::string_view text = s;
assert(entries.size() < std::size_t(maximum) && text.size() <= std::size_t(maximum));
entries.push_back({text, {}});
}
return entries;
}
inline int compare(Entry &first, Entry &second) {
Entry *a = &first, *b = &second;
bool reverse = a->text.size() > b->text.size();
if (reverse) std::swap(a, b);
int n = int(a->text.size()), m = int(b->text.size());
int value = std::char_traits<char>::compare(a->text.data(), b->text.data(), n);
if (!value && n < m) {
if (m - n <= n) value = std::char_traits<char>::compare(b->text.data(), b->text.data() + n, m - n);
else {
int common;
if (b->z.empty() && b->compared < 8 * std::uint64_t(m)) {
common = sequence_compare_detail::prefix(b->text.data(), b->text.data() + n, m - n);
b->compared += common + 1;
} else {
if (b->z.empty()) {
b->z.resize(m);
auto s = b->text;
sequence_match_detail::z_table(m, b->z.data(), [&](int i, int j, int limit) {
return sequence_compare_detail::prefix(s.data() + i, s.data() + j, limit);
});
}
common = b->z[n];
}
if (common < m - n) value = int(static_cast<unsigned char>(b->text[common])) - int(static_cast<unsigned char>(b->text[n + common]));
}
if (!value) value = std::char_traits<char>::compare(b->text.data() + m - n, a->text.data(), n);
}
int result = (value > 0) - (value < 0);
return reverse ? -result : result;
}
inline std::vector<int> order(std::vector<Entry> &entries, bool descending) {
std::vector<int> result;
result.reserve(entries.size());
for (int i = 0; i < int(entries.size()); ++i) if (entries[i].text.empty()) result.push_back(i);
std::size_t empty = result.size();
for (int i = 0; i < int(entries.size()); ++i) if (!entries[i].text.empty()) result.push_back(i);
std::size_t n = result.size() - empty;
if (n <= 1) return result;
std::vector<int> buffer(n);
int *source = result.data() + empty, *target = buffer.data();
for (std::size_t width = 1; width < n; width *= 2) {
for (std::size_t left = 0; left < n; left += 2 * width) {
std::size_t middle = std::min(left + width, n), right = std::min(left + 2 * width, n);
if (middle < right) {
int value = compare(entries[source[middle - 1]], entries[source[middle]]);
if (descending ? value >= 0 : value <= 0) {
std::copy(source + left, source + right, target + left);
continue;
}
}
std::size_t i = left, j = middle, k = left;
while (i < middle && j < right) {
int value = compare(entries[source[i]], entries[source[j]]);
bool take_right = descending ? value < 0 : value > 0;
target[k++] = take_right ? source[j++] : source[i++];
}
while (i < middle) target[k++] = source[i++];
while (j < right) target[k++] = source[j++];
}
std::swap(source, target);
}
if (source != result.data() + empty) std::copy_n(source, n, result.data() + empty);
return result;
}
template <Strings Range>
std::string concatenate(const Range &strings, bool descending) {
auto entries = prepare(strings);
auto indices = order(entries, descending);
std::string result;
std::size_t length = 0;
for (const auto &entry : entries) {
if (entry.text.size() > result.max_size() - length) throw std::length_error("concatenation");
length += entry.text.size();
}
result.reserve(length);
for (int i : indices) if (!entries[i].text.empty()) result.append(entries[i].text);
return result;
}
}
template <concatenation_order_detail::Strings Range>
std::vector<int> concatenation_order(const Range &strings, bool descending = false) {
auto entries = concatenation_order_detail::prepare(strings);
return concatenation_order_detail::order(entries, descending);
}
template <concatenation_order_detail::Strings Range>
std::string min_concatenation(const Range &strings) {
return concatenation_order_detail::concatenate(strings, false);
}
template <concatenation_order_detail::Strings Range>
std::string max_concatenation(const Range &strings) {
return concatenation_order_detail::concatenate(strings, true);
}