Loading...
6/24/2025
整数列が与えられます。について、以下の問題を解いて下さい。
問題をそのまま解くのは不可能なので、代わりに各値について寄与を考える。すなわち、が長さの連続部分列の最大値として現れるのは何回か?を考えれば良い。
が長さの連続部分列の最大値として現れることを考えるために、以下の要素が、の左右にいくつ伸びているか?を知りたい。これはstd::set
とlower_bound
を用いることで問題なく取得することができる。以降、をかつを満たす最小値、をかつを満たす最大値として定義する。
この区間において、を含んでかつ長さの連続部分列がいくつ存在するかを考えよう1。 まず、である間は、ちょうど個の連続部分列が存在する。これは左右にいくらでも伸ばせるので、を長さのうち何番目に置くか?という話を考えれば良い。次にである間、片端がより伸ばせず、もう片方はいくらでも伸ばせるのでのままである。最後にの場合は、両端に自由度がなくなっていくので、となる。
答えに対する寄与の回数を、例えばで考えると、となる。これは左右が等差数列、真ん中が同じ値になっていて簡単に区間演算をするのが難しい2が、「増加部分の+と減少部分の-を区間加算」ができればimos法で求めることができるので、区間加算の遅延セグメントツリーを用いれば良い3。
以上を実装することで、全体で時間で解くことができる。提出(C++, 249ms)
// 区間加算にしか興味がないので、Sの演算は何でもOK using S = long long; using F = long long; const S INF = 8e18; S op(S a, S b){ return std::min(a, b); } S e(){ return INF; } S mapping(F f, S x){ return f+x; } F composition(F f, F g){ return f+g; } F id(){ return 0; } void solve() { int n; cin>> n; auto a = i64_vec_IN(n); vll indicate(n,0); iota(all(indicate), 0); sort(all(indicate), [&](const ll& i, const ll& j) { if(a[i] == a[j]) return i>j; else return a[i]<a[j]; }); lazy_segtree<S,op,e,F,mapping,composition,id> seg(n+1); rep(i,n) seg.set(i,0); set<ll> notChecked; rep(i,n) notChecked.insert(i); notChecked.insert(-1); notChecked.insert(n); for(auto i: indicate) { notChecked.erase(i); auto itr = notChecked.upper_bound(i); ll right = *itr; itr--; ll left = *itr; ll r_d = right - i; ll l_d = i - left; ll itrval = r_d + l_d - 1; seg.apply(0, min(l_d, r_d), a[i]); seg.apply(max(l_d, r_d), itrval+1, -a[i]); } ll cur = 0; vll ans(n); rep(i,n) { cur += seg.get(i); ans[i] = cur; } rep(i,n) cout << ans[i] << " "; }