ECR150-E Fill the Matrix

問題

https://codeforces.com/contest/1841/problem/E (diff: 2200)

 n n列の行列がある。始め i列目の 1行目から a_{i}行目のマスは黒く、 a_{i + 1}行目から n行目のマスは白く塗られている。各マスには値が決められていない。

あなたは、この行列から m個のマスを選んで 1から mの値を割り当てる。

 (i, j) kが、 (i, j + 1) k + 1が割り当てられるような kの数は最大で何個になり得るか求めよ

制約

  •  1\ \le\ n\ \le\ 2\times 10^{5}
  •  mは白マスの数を超えない

解法

 i個横に連続した白ますに値 k, k + 1, k + 2, \dots, k + i - 1をこの順で割り当てたとする。このとき値 k + i - 1だけ解に寄与しない。

このような無駄になってしまう分を減らすには、白ますがより長く連続しているものから貪欲に割り当てれば良い。

白マスの連結成分の長さを列挙 -> ソート とするとTLEしてしまう。

そこで白マスの長方形は一気に使うこととしてシミュレーションを高速化しよう。

実装

#include <bits/stdc++.h>

#include "Src/Template/IOSetting.hpp"
#include "Src/DataStructure/SparseTable/SparseTable.hpp"
using namespace zawa;

struct M {
    using Element = int;
    static int identity() {
        return 0;
    }
    static int operation(int l, int r) {
        return std::max(l, r);
    }
};

long long solve() {
    int n; std::cin >> n;
    std::vector<int> a(n);
    for (auto& x : a) std::cin >> x;
    long long m; std::cin >> m;
    std::vector<std::set<int>> b(n + 1);
    for (int i{} ; i < n ; i++) b[a[i]].insert(i);
    SparseTable<M> spt(a);
    using qt = std::tuple<int, int, int>;
    std::priority_queue<qt> que;
    for (int i{} ; i < n ; i++) {
        if (a[i] < n) {
            int l{i};
            while (i < n and a[i] < n) i++;
            int r{i};
            que.emplace(r - l, l, n);
        }
    }
    long long ans{};
    while (m) {
        auto [len, l, dn]{que.top()};
        int r{l + len};
        que.pop();
        if (len <= 0) continue;
        auto up{spt.product(l, r)};
        // std::cout << "cur " << m << std::endl;
        // std::cout << "use " << l << ' ' << r << ' ' << dn << ' ' << up << std::endl;
        { // 計算
            long long tate{std::min<long long>(dn - up, m / len)};
            // std::cout << len - 1 << ' ' << tate << std::endl;
            ans += (long long)(len - 1) * tate; 
            m -= (long long)len * tate;
            assert(m >= 0);
            if (tate < dn - up and m > 0) {
                assert(m < len);
                ans += m - 1;
                m = 0;
            }
        }
        if (up > 0) {
            int L{l};
            for (auto it{b[up].lower_bound(l)} ; it != b[up].end() and *it < r ; it++) {
                int R{*it};
                if (R - L > 0) {
                    que.emplace(R - L, L, up);
                }
                L = R + 1;
                if (std::next(it) == b[up].end() or *std::next(it) >= r) {
                    if (r - L > 0) {
                        que.emplace(r - L, L, up);
                    }
                    break; 
                }
            }
        }
    }
    return ans;
}
int main() {
    SetFastIO();

    int t; std::cin >> t;
    while (t--) {
        std::cout << solve() << '\n';
    }
}

感想

  • バチャ中は挑む時間すら存在せず。その後ぼんやり考えて自力AC。
  • 考察はあまり難しくない。実装が本質の問題だった。