D - Draw Your Cards 解説 by kyopro_friends
場に見えているカードの値をキーに、その山に含まれるカードの配列を値に持つ連想配列を考えます。
すなわち、例えば場に [3,2,1], [12,11], [7] という 3 つの山があるとき、
1 → [3,2,1]
11 → [12,11]
7 → [7]
と引けるような連想配列を用意します。この連想配列を適切に更新することで、問題を解くことができます。
例えば、上の例において、新たに 10 のカードを引いた場合、[12,11] の山の上に 10 のカードが置かれ、操作後の連想配列は次のような状態になっている必要があります。
1 → [3,2,1]
10 → [12,11,10]
7 → [7]
これは、
- 二分探索によりキー 11 を見つける
- キー 11 の持つ値 [12,11] を新たなキー 10 に割り当て直す
- キー 10 の持つ値 [12,11] の末尾に 10 を追加する
という手順により行うことができます。言語によっては、キーを割り当て直す際に配列のコピーが行われ、計算量が悪化することがあります。例えばC++であればmoveを用いることで、コピーを行うことなく参照を奪うことができます。(筆者はC++に詳しくないため、適切な説明は他のサイトに譲ります)
カード1枚を場に出す処理にかかる時間は、キーを見つける二分探索に \(O(\log N)\) 、その他は(ならし) \(O(1)\) であるため、\(N\) 枚合わせて \(O(N\log N)\) となります。カードを食べる部分に関する操作も全て合わせて \(O(N\log N)\) 時間で行えることが確かめられるので、全体の計算量も \(O(N\log N)\) となります。
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,k;
cin >> n >> k;
vector<int>ans(n+1,-1);
map<int,vector<int>>yama;
for(int i=1;i<=n;i++){
int x;
cin >> x;
auto it=yama.lower_bound(x);
if(it!=yama.end()){
yama[x]=move(it->second);
yama.erase(it);
}
yama[x].push_back(x);
if(yama[x].size()==k){
for(auto y:yama[x])ans[y]=i;
yama.erase(x);
}
}
for(int i=1;i<=n;i++)cout << ans[i] << endl;
}
以下のPythonの実装例は、クエリあたり \(O(\sqrt{N})\) になるSortedSetを用いているため、計算量は\(O(N^{1.5})\) です。
実装例(Python)
投稿日時:
最終更新: