ardririyの足跡

ABC407F - Sums of Sliding Window Maximum

ABC407F - Sums of Sliding Window Maximum

問題

整数列AAが与えられます。k=1,2,,Nk=1, 2, \cdots, Nについて、以下の問題を解いて下さい。

  • AAの長さkkの連続部分列はNk+1N-k+1個存在するが、それぞれについての最大値を合計したものを求めよ。

解法

問題をそのまま解くのは不可能なので、代わりに各値について寄与を考える。すなわち、AiA_iが長さkkの連続部分列の最大値として現れるのは何回か?を考えれば良い。

AiA_iが長さkkの連続部分列の最大値として現れることを考えるために、AiA_i以下の要素が、AiA_iの左右にいくつ伸びているか?を知りたい。これはstd::setlower_boundを用いることで問題なく取得することができる。以降、ll1li1\leq l \leq iかつmaxj=liAj=Ai\max_{j=l}^i A_j = A_iを満たす最小値、rri+rNi+r \leq Nかつmaxj=ii+rAi+j=Ai+r\max_{j=i}^{i+r} A_{i+j} = A_{i+r}を満たす最大値として定義する。

この区間において、iiを含んでかつ長さkkの連続部分列がいくつ存在するかを考えよう1。 まず、kmin(l,r)k \leq \text{min}(l, r)である間は、ちょうどkk個の連続部分列が存在する。これは左右にいくらでも伸ばせるので、iiを長さkkのうち何番目に置くか?という話を考えれば良い。次にmin(l,r)<kmax(l,r)\min(l,r) < k \leq \max(l,r)である間、片端がmin(l,r)\min(l,r)より伸ばせず、もう片方はいくらでも伸ばせるのでmin(l,r)\min(l,r)のままである。最後にmax(l,r)<k\max(l,r) < kの場合は、両端に自由度がなくなっていくので、min(l,r)(kmax(l,r))\min(l,r)-(k-\max(l,r))となる。

答えCk(k=1,2,,N)C_k(k=1,2,\cdots,N)に対する寄与の回数を、例えばl=4,r=2l=4,r=2で考えると、C1=1,C2=2,C3=2,C4=2,C5=1,C6=0,C_1 = 1, C_2 = 2, C_3 = 2, C_4 = 2, C_5 = 1, C_6 = 0, \cdotsとなる。これは左右が等差数列、真ん中が同じ値になっていて簡単に区間演算をするのが難しい2が、「増加部分の+と減少部分の-を区間加算」ができればimos法で求めることができるので、区間加算の遅延セグメントツリーを用いれば良い3

以上を実装することで、全体でO(NlogN)O(N\log N)時間で解くことができる。提出(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] << "
";
}

Footnotes

  1. 自由度を考えていく感じで、まぁいくつかの具体例を手で試すのが良さそう。

  2. 遅延セグメントツリーでできるらしい。ref(解説)

  3. 公式解説では2回imos法を使うことでこれを実現しているが、ぼくは頭を壊しそうなのでやりたくなかった