AtCoder ARC 115 E

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtCoder ARC 115 E相关的知识,希望对你有一定的参考价值。

问题链接:Here

长度为 \\(N\\) 的数列 \\(A_1,…,A_N\\) 。回答满足以下条件的长度 \\(N\\) 的数列 \\(X_1,…,X_N\\) 的个数除以 \\(998244353\\) 的余数。

  • \\(1\\le X_i \\le A_i\\)
  • \\(X_i \\not = X_{i + 1}\\)

\\(2\\le N \\le 5e5,1\\le A_i \\le 1e9\\)


因为 $X_i \\not = X_{i + 1} $ 的条件比较难处理,所以用容斥定理来做更好点

如果有 \\(K\\) 个相邻的相等,那么容斥系数为 \\((-1)^k\\)

那我们把 \\(n\\) 分为若干连续相同段,然后每一段容斥系数分开算,这样就是一个 dp 的转移式子了。

\\[f_i = \\sum_{j=0}^{i-1}f_j\\times min\\{A_k\\} (k\\in(j,i]\\times(-1)^{i-j-1}) \\]

这个 \\(min\\{A_k\\}\\) 每次加入一个新的时候会影响一个后缀,用单调栈找到这个后缀,然后把 \\(f_i\\) 丢进线段树里。

而那个容斥系数就是每次整个线段树乘上一个 (-1),这个丢到外面处理就好了

  • \\(\\mathcal{O}(N\\ log\\ N)\\)
using ll = long long;
using namespace std;
const ll N = 5e5 + 10, M = N << 2, P = 998244353;
ll n, a[N], q[N], f[N];
ll w[M], lazy[M], v[M];
void Downdata(ll x) {
    if (!lazy[x])return;
    w[x * 2] = v[x * 2] * lazy[x] % P;
    w[x * 2 + 1] = v[x * 2 + 1] * lazy[x] % P;
    lazy[x * 2] = lazy[x * 2 + 1] = lazy[x];
    return;
}
void Change(ll x, ll L, ll R, ll l, ll r, ll c) {
    if (L == l && R == r) {lazy[x] = c; w[x] = v[x] * c % P; return;}
    ll mid = (L + R) >> 1; Downdata(x);
    if (r <= mid)Change(x * 2, L, mid, l, r, c);
    else if (l > mid)Change(x * 2 + 1, mid + 1, R, l, r, c);
    else Change(x * 2, L, mid, l, mid, c), Change(x * 2 + 1, mid + 1, R, mid + 1, r, c);
    w[x] = (w[x * 2] + w[x * 2 + 1]) % P; v[x] = (v[x * 2] + v[x * 2 + 1]) % P; return;
}
void Insert(ll x, ll L, ll R, ll pos, ll c) {
    if (L == R) {v[x] = c; w[x] = c * lazy[x] % P; return;}
    ll mid = (L + R) >> 1; Downdata(x);
    if (pos <= mid)Insert(x * 2, L, mid, pos, c);
    else Insert(x * 2 + 1, mid + 1, R, pos, c);
    w[x] = (w[x * 2] + w[x * 2 + 1]) % P; v[x] = (v[x * 2] + v[x * 2 + 1]) % P; return;
}
signed main() {
    scanf("%lld", &n);
    ll top = 1; Insert(1, 1, n, 1, P - 1);
    for (ll i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        while (top > 0 && a[i] < a[q[top]])top--;
        Change(1, 1, n, q[top] + 1, i, a[i]); q[++top] = i;
        f[i] = (i & 1) ? (P - w[1]) : w[1];
        if (i != n)Insert(1, 1, n, i + 1, P - w[1]);
    }
    printf("%lld\\n", f[n]);
    return 0;
}

但是这道题是可以线性实现的,题目这个一个点可以控制一段区间的性质,可以考虑用 笛卡尔树 维护,维护一个栈、栈中存储 \\(A_i\\)​ 的下标和控制的权值两样东西,然后就可以根据奇偶性计算即可

  • \\(\\mathcal{O}(N)\\)
using ll = long long;

// modint
template<int MOD> struct Fp {
    ll val;
    constexpr Fp(ll v = 0) noexcept : val(v % MOD) {
        if (val < 0) val += MOD;
    }
    constexpr int getmod() const { return MOD; }
    constexpr Fp operator - () const noexcept {
        return val ? MOD - val : 0;
    }
    constexpr Fp operator + (const Fp &r) const noexcept { return Fp(*this) += r; }
    constexpr Fp operator - (const Fp &r) const noexcept { return Fp(*this) -= r; }
    constexpr Fp operator * (const Fp &r) const noexcept { return Fp(*this) *= r; }
    constexpr Fp operator / (const Fp &r) const noexcept { return Fp(*this) /= r; }
    constexpr Fp &operator += (const Fp &r) noexcept {
        val += r.val;
        if (val >= MOD) val -= MOD;
        return *this;
    }
    constexpr Fp &operator -= (const Fp &r) noexcept {
        val -= r.val;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr Fp &operator *= (const Fp &r) noexcept {
        val = val * r.val % MOD;
        return *this;
    }
    constexpr Fp &operator /= (const Fp &r) noexcept {
        ll a = r.val, b = MOD, u = 1, v = 0;
        while (b) {
            ll t = a / b;
            a -= t * b, swap(a, b);
            u -= t * v, swap(u, v);
        }
        val = val * u % MOD;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr bool operator < (const Fp &r) const noexcept {
        return this->val < r.val;
    }
    constexpr bool operator == (const Fp &r) const noexcept {
        return this->val == r.val;
    }
    constexpr bool operator != (const Fp &r) const noexcept {
        return this->val != r.val;
    }
    friend constexpr istream &operator >> (istream &is, Fp<MOD> &x) noexcept {
        is >> x.val;
        x.val %= MOD;
        if (x.val < 0) x.val += MOD;
        return is;
    }
    friend constexpr ostream &operator << (ostream &os, const Fp<MOD> &x) noexcept {
        return os << x.val;
    }
    friend constexpr Fp<MOD> modpow(const Fp<MOD> &r, ll n) noexcept {
        if (n == 0) return 1;
        if (n < 0) return modpow(modinv(r), -n);
        auto t = modpow(r, n / 2);
        t = t * t;
        if (n & 1) t = t * r;
        return t;
    }
    friend constexpr Fp<MOD> modinv(const Fp<MOD> &r) noexcept {
        ll a = r.val, b = MOD, u = 1, v = 0;
        while (b) {
            ll t = a / b;
            a -= t * b, swap(a, b);
            u -= t * v, swap(u, v);
        }
        return Fp<MOD>(u);
    }
};

const int MOD = 998244353;
using mint = Fp<MOD>;
using pll = pair<ll, ll>;

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n;
    cin >> n;
    vector<ll> a(n);
    for (int i = 0; i < n; ++i) cin >> a[i];

    stack<pll> st;
    st.push({0, 0});
    vector<mint> dp(n + 1, 0), sdp(n + 2, 0);
    dp[0] = 1, sdp[1] = 1;
    for (int i = 1; i <= n; ++i) {
        ll t = a[i - 1];
        while (!st.empty() && st.top().first >= t) st.pop();
        ll num = st.top().second;
        st.push({t, i});

        if (num > 0) dp[i] += dp[num];
        dp[i] -= (sdp[i] - sdp[num]) * t;
        sdp[i + 1] = sdp[i] + dp[i];
    }

    mint ans = dp[n];
    if (n & 1) ans = -ans;
    cout << ans;
}

看了一下在推特上这道题的讨论发现线段树写法上还可以在坐标压缩一下,思路来源于 @opt_cp

The desire of his soul is the prophecy of his fate
你灵魂的欲望,是你命运的先知。

以上是关于AtCoder ARC 115 E的主要内容,如果未能解决你的问题,请参考以下文章

Plus Matrix atcoder arc115 TB

Atcoder ARC101 E 树dp

AtCoder Regular Contest (ARC102) E - Stop. Otherwise... 排列组合 动态规划

AtCoder Regular Contest 100 (ARC100) E - Or Plus Max 其他

AtCoder Beginner Contest 115 Solution

AtCoder arc078_d Mole and Abandoned Mine