Official
M - 名前の変更/Deadnames Editorial by penguinman
問題の内容を簡略化すると、以下のようになります。
\(N\) 個の要素からなる集合があり、\(i\) 個目の要素は \((S_i,i)\) である。以下のクエリを \(Q\) 個順に処理したあとの各要素を出力せよ。
- \(N\) 個の要素を \(S_i\) の昇順 → \(i\) の昇順、という優先順位で並べたとき、\(X_i\) 番目に来る要素が \((x,y)\) であるとする。集合から \((x,y)\) を削除し、代わりに \((T_i,y)\) を挿入する。
よってこの問題の本質は、以下の \(3\) つの処理をそれぞれ高速に行うこととなります。
- 集合に含まれる要素を適当な順序で並べたとき、\(k\) 番目に来る要素を求める。
- 集合から要素を削除する。
- 集合に要素を挿入する。
このような処理は、平衡二分探索木と呼ばれるデータ構造を用いることでそれぞれ \(O(\log N)\) で行うことが可能です。
C++ の場合、g++ 拡張の pb_ds (policy based data structure) を利用すると便利です。
実装例 (C++, Tester解)
#include <bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
void fast_io() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
}
template <class T>
using binary_search_tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
int main() {
fast_io();
int n, q;
cin >> n >> q;
vector<string> s(n);
for (int i = 0; i < n; ++i) {
cin >> s[i];
}
vector<int> x(q);
vector<string> t(q);
for (int i = 0; i < q; ++i) {
cin >> x[i] >> t[i];
}
vector<string> c;
c.reserve(s.size() + t.size());
copy(begin(s), end(s), back_inserter(c));
copy(begin(t), end(t), back_inserter(c));
sort(begin(c), end(c));
c.erase(unique(begin(c), end(c)), end(c));
vector<int> s_id(n);
for (int i = 0; i < n; ++i) {
s_id[i] = distance(begin(c), lower_bound(begin(c), end(c), s[i]));
}
vector<int> t_id(q);
for (int i = 0; i < q; ++i) {
t_id[i] = distance(begin(c), lower_bound(begin(c), end(c), t[i]));
}
binary_search_tree<pair<int, int>> set;
for (int i = 0; i < n; ++i) {
set.insert({s_id[i], i});
}
for (int i = 0; i < q; ++i) {
auto itr = set.find_by_order(x[i] - 1);
int k = itr->second;
s_id[k] = t_id[i];
set.erase(itr);
set.insert({s_id[k], k});
}
for (int i = 0; i < n; ++i) {
cout << c[s_id[i]] << " \n"[i + 1 == n];
}
return 0;
}
なお、Trie 木を用いることで各処理を(\(σ=26\) として)\(O(σ)\) で行い、この問題に AC することも可能です。
実装例 (C++, Writer解)
#include<bits/stdc++.h>
using namespace std;
// trie
struct trie{
int maxi;
vector<int> node;
vector<vector<int>> next;
trie(int el_sz): maxi(el_sz){
node.resize(1);
next.emplace_back(vector<int>(maxi, -1));
}
void insert(vector<int> A){
int now = 0;
for(auto el: A){
if(next[now][el] == -1){
next[now][el] = node.size();
node.emplace_back(0);
next.emplace_back(vector<int>(maxi,-1));
}
now = next[now][el];
node[now]++;
}
}
// 1-indexed
vector<int> kth_element(int k){
int now = 0;
vector<int> ret;
while(true){
bool flag = false;
for(int i=0; i<maxi; i++){
if(next[now][i] == -1) continue;
if(k <= node[next[now][i]]){
flag = true;
ret.emplace_back(i);
now = next[now][i];
break;
}
else k -= node[next[now][i]];
}
if(!flag) break;
}
return ret;
}
void erase(vector<int> A){
int now = 0;
for(auto el: A){
now = next[now][el];
node[now]--;
}
}
};
vector<int> translate(string S, int idx){
vector<int> ret;
for(auto el: S) ret.emplace_back(el-'a'+10);
string idx_ = to_string(idx);
for(int i=idx_.size(); i<5; i++) ret.emplace_back(0);
for(auto el: idx_) ret.emplace_back(el-'0');
return ret;
}
pair<string,int> inverse(vector<int> A){
string S = "";
int idx = 0;
for(auto el: A){
if(10 <= el) S += 'a'+el-10;
else idx = idx*10+el;
}
return make_pair(S,idx);
}
int main(){
int N,Q; cin >> N >> Q;
vector<string> S(N);
for(int i=0; i<N; i++) cin >> S[i];
trie tr(36);
for(int i=0; i<N; i++) tr.insert(translate(S[i],i));
while(Q--){
int X; cin >> X;
string T; cin >> T;
auto A = tr.kth_element(X);
auto p = inverse(A);
tr.erase(A);
tr.insert(translate(T,p.second));
}
vector<string> ans(N);
for(int i=0; i<N; i++){
auto A = tr.kth_element(1);
auto p = inverse(A);
tr.erase(A);
ans[p.second] = p.first;
}
cout << ans[0];
for(int i=1; i<N; i++) cout << " " << ans[i];
cout << "\n";
}
posted:
last update: