D - フェンスの塗り替え / Repainting the Fence 解説
by
kyopro_friends
AIによる解説には計算量が \(O((N+M)\alpha(N))\) と書かれていますが誤りです。AIの実装はいずれも Union By Size/Union By Rank なしで Path Halving/Path Compression をしているため、計算量は find 1 回あたりならし \(O(\log N)\) としか示せません。全体で \(O((N+M)\log N)\) となります。
解法1:DSU
色を塗る操作を逆から処理することにします。一度色が塗られたマスの色が上書きされることはないので、「まだ色を塗っていない次のマス」を高速に見つけることができれば、全体を高速に処理できます。
DSU を用いて、各マスに対応する頂点が「まだ色を塗っていない次のマス」を指す有向木を管理することでならし \(O(\log N)\) 時間でそのようなマスを見つけることができます。
計算量は \(O((N+M)\log N)\) になります。
実装例 (C++)
#include<bits/stdc++.h>
using namespace std;
struct dsu {
dsu(int n) : parent(n, -1) {}
void merge(int a, int b) {
int x = leader(a), y = leader(b);
if (x == y) return;
if(x < y) swap(x, y);
parent[y] = x;
}
int leader(int a) {
if (parent[a] < 0) return a;
return parent[a] = leader(parent[a]);
}
vector<int> parent;
};
int main(){
int n, m;
cin >> n >> m;
vector<array<int,3>>query(m);
for(int i=0; i<m; i++){
cin >> query[i][0] >> query[i][1] >> query[i][2];
query[i][0]--;
}
reverse(query.begin(), query.end());
dsu d(n+1);
vector<int>ans(n);
for(auto[l, r, c]: query){
while(l < r){
if(ans[l] == 0){
ans[l] = c;
d.merge(l, l+1);
}
l = d.leader(l);
}
}
for(int i=0; i<n; i++){
cout << ans[i] << endl;
}
}
Pythonの自然な実装では通せませんでした(2ケース TLE)
解法2:区間を set で管理するテク
色を塗る操作を逆から処理することにします。一度色が塗られたマスの色が上書きされることはないので、「まだ色を塗っていない次のマス」を高速に見つけることができれば、全体を高速に処理できます。
すでに色が塗られているマスの極大な区間を set で管理し、二分探索することで「まだ色を塗っていない次のマス」を高速に見つけることができます。
計算量は \(O((N+M)\log N)\) になります。(python の SortedSet はそれより計算量が悪いですが、この問題ではACすることができます)
実装例 (C++)
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, m;
cin >> n >> m;
vector<array<int,3>>query(m);
for(int i=0; i<m; i++){
cin >> query[i][0] >> query[i][1] >> query[i][2];
query[i][0]--;
}
reverse(query.begin(), query.end());
set<pair<int,int>>s;
vector<int>ans(n);
for(auto[l, r, c]: query){
int crr = l;
while(crr < r){
if(ans[crr] == 0){
ans[crr] = c;
crr++;
}else{
auto it = s.lower_bound({crr,-1});
if(it != s.end() && it -> first == crr){
crr = it -> second;
}else{
it--;
crr = it -> second;
}
}
}
// [l,r) と重なる区間をマージしながら削除
int right = r;
int left = l;
auto it = s.upper_bound({l, n+1});
if(it != s.begin())it--;
while(it != s.end() && it -> first <= right){
if(it -> second >= left){
left = min(left, it -> first);
right = max(right, it -> second);
it = s.erase(it);
}else{
it++;
}
}
s.insert({left, right});
}
for(int i=0; i<n; i++){
cout << ans[i] << endl;
}
}
実装例 (Python)
from sortedcontainers import SortedSet
N, M = map(int, input().split())
query = []
for _ in range(M):
L, R, C = map(int, input().split())
query.append((L - 1, R, C))
query.reverse()
ans = [0] * N
s = SortedSet()
for L, R, C in query:
crr = L
while crr < R:
if ans[crr] == 0:
ans[crr] = C
crr += 1
else:
idx = s.bisect_left((crr, -1))
if idx < len(s) and s[idx][0] == crr:
crr = s[idx][1]
else:
idx -= 1
crr = s[idx][1]
# [L,R) と重なる区間をマージしながら削除
left, right = L, R
idx = s.bisect_right((L, N + 1))
if idx > 0:
idx -= 1
while idx < len(s):
L, R = s[idx]
if L > right:
break
if R >= left:
left = min(left, L)
right = max(right, R)
s.pop(idx)
else:
idx += 1
s.add((left, right))
print(*ans)
解法3:遅延セグメント木
区間代入操作が可能な遅延セグメント木を用いることで、問題文の指示通りクエリを高速に処理することができます。計算量は \(O((N+M)\log N)\) になります。
実装例 (C++)
#include<bits/stdc++.h>
#include<atcoder/lazysegtree>
using namespace std;
using X = int;
using F = pair<int,int>;
X xx(X x, X y){return 0;}
X xid(){return 0;}
X fx(F f, X x){return f.first * x + f.second;}
F ff(F f, F g){return {f.first * g.first, f.first * g.second + f.second};}
F fid(){return {1, 0};}
int main(){
int n,m;
cin >> n >> m;
atcoder::lazy_segtree<X, xx, xid, F, fx, ff, fid>seg(n);
for(int i=0; i<m; i++){
int l, r, c;
cin >> l >> r >> c;
seg.apply(l-1, r, {0,c});
}
for(int i=0; i<n; i++)
cout << seg.get(i) << ' ';
}
}
実装例 (Python)
from atcoder.lazysegtree import LazySegTree
def xx(x, y): return 0
xid = 0
def fx(f, x): return f[0] * x + f[1]
def ff(f, g): return (f[0] * g[0], f[0] * g[1] + f[1])
fid = (1, 0)
N, M = map(int, input().split())
seg = LazySegTree(xx, xid, fx, ff, fid, N)
for _ in range(M):
L, R, C = map(int,input().split())
seg.apply(L-1, R, (0, C))
ans = [seg.get(i) for i in range(N)]
print(*ans)
投稿日時:
最終更新:
