P13662 「TPOI-5A」Luminescence

“快看,好美的烟花!”

个人记录第二十五篇。

题目描述

给定 \(n\) 与两个长度为 \(n\) 的序列 \(a,b\)。定义一个 \(0\sim n-1\) 的排列 \(q\)魔怔的,当且仅当:

  • \(\forall 1\le k\le n,\min^k_{i=1}q_i=a_k\)
  • \(\forall 1\le k\le n,\min^n_{i=k}q_i=b_k\)

定义一个排列 \(q\) 的权值为 \(\sum_{1\le l\le r\le n}\operatorname{mex}_{l\le i\le r}q_i\),求所有魔怔的排列的权值之和。答案对 \(998244353\) 取模。

一个集合 \(M\)\(\operatorname{mex}(M)\) 定义为最小的没有在 \(M\) 中出现的自然数。如 \(\text{mex}\{1,2,3,4\}=0,\text{mex}\{0,1,3,4\}=2\)

Solution

烟花确实是很美的呢,但是我的做法似乎并不够美。

做法 \(1\)

参加过联合省选 2026 的人都知道,一个排列的区间 \(\operatorname{mex}\) 等于补集 \(\min\)。所以我们考虑将题目中的和式转换一下:

\[\begin{align*} W=&\sum_{1\le l\le r\le n}\operatorname{mex}_{l\le i\le r} q_i\\ =&\sum_{1\le l\le r\le n}\min(\min_{1\le i<l}q_i,\min_{r+1\le i\le n}q_i)\\ =&\sum_{1\le l\le r\le n}\min(a_{l-1},b_{r+1}) \end{align*} \]

注意到

\[\ \min(x, y) = \begin{cases} y, & y \le x \\ x, & y > x \end{cases} \ \]

所以

\[\begin{align*} &\sum_{1\le l\le r\le n}\min(a_{l-1},b_{r+1})\\ =&\sum_{l=0}^{n-1}\sum\limits_{r=l+2}^{n+1}\min(a_{l},b_{r})\\ =&\sum_{l=0}^{n-1}\left(\sum_{r=l+2}^{n+1}b_r[b_r\le a_l]+a_l|S|\right) \end{align*} \]

其中集合 \(S=\{r\mid b_r>a_l\}\)

容易发现这实际上是一个二维偏序问题,直接树状数组维护 \(b_r\le a_l\)\(b_r\) 的和还有集合 \(S\) 的大小(也就是满足 \(b_r>a_l\)\(r\) 的个数)即可。

注意,为了保证满足题意,我们可以令 \(a_0=b_{n+1}=n\)

但是这只是其中一个排列 \(q\) 的权值,但又由于每一个 \(q\) 的权值都是这么多,所以最终答案是 \(W\) 乘上合法的 \(q\) 的个数。

考虑计算 \(q\) 的数量。

由于 \(q\) 的前缀最小值等于 \(a\),所以对前缀最小值来说,如果 \(a_i < a_{i-1}\),意味着在位置 \(i\) 处最小值下降了,那么 \(q_i\) 必须恰好等于那个更小的值 \(a_i\),否则无法产生新的最小值。

同理,对后缀最小值,如果 \(b_i < b_{i+1}\),则 \(q_i\) 必须等于 \(b_i\)

所以:

  • \(a_i < a_{i-1}\) 时,令 \(q_i = a_i\)
  • \(b_i < b_{i+1}\) 时,令 \(q_i = b_i\)

其余位置可以填任何尚未使用的数字。

对每个自由位置 \(i\),可以填入的数字 \(x\) 必须满足:

  • 不会改变前缀最小值,即 \(x \ge a_{i-1}\)(否则前缀最小值会变小);
  • 不会改变后缀最小值,即 \(x \ge b_{i+1}\)(否则后缀最小值会变小)。
    因此 \(x\) 需要满足 \(x\ge\max(a_{i-1},b_{i+1})\)。设 \(L_i=\max(a_{i-1},b_{i+1})\)

并且 \(x\) 不能是已被强制占用的数字。

设剩余未使用的数字集合为 \(S\),自由位置集合为 \(P\),每个位置 \(i\in P\) 有下界 \(L_i\)

我们需要将 \(S\) 中的数字一一分配给 \(P\) 中的位置,且每个位置 \(i\) 分配的数字大于等于 \(L_i\)

我们可以将 \(P\) 中的下界从大到小排序,记为 \(L_{(1)}\ge L_{(2)}\ge\cdots\ge L_{(k)}\)

处理第 \(t\) 个位置(\(t\)\(0\) 开始)时,候选数字是 \(S\) 中大于等于 \(L_{(t)}\) 且尚未被选走的那些。设 \(S\) 中大于等于 \(v\) 的元素个数为 \(\text{suf}_v\)(值域后缀和),则第 \(t\) 个位置的候选数量为

\[A_t=\text{suf}_{L_{(t)}}-t \]

因为前面 \(t\) 个位置已经选走了 \(t\) 个数字,并且这些数字一定都大于等于当前下界,否则它们无法填入之前更大的下界。

所以全部位置方案数相乘就得到了排列 \(q\) 的数量。

Code
int n;
int a[N], b[N];
int q[N];
bool used[N];
struct BIT {
    mo sum[N], cnt[N];
    inline void init (int n) {for (int i = 0; i <= n + 100; ++ i) sum[i] = cnt[i] = 0;}
    inline int lowbit (int x) {return x & -x;}
    inline void Add (int x) {int v = x; ++ x; for (; x <= n + 1; x += lowbit (x)) ++ cnt[x], sum[x] += v;}
    inline pair <mo, mo> Sum (int x) {
        pair <mo, mo> ans = {0, 0};
        ++ x;
        for (; x >= 1; x -= lowbit (x)) ans.first += sum[x], ans.second += cnt[x];
        return ans;
    }
}bit;

inline void solve () {
    cin >> n;
    bit.init (n);
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) cin >> b[i];
    a[0] = b[n + 1] = n;
    mo W = 0;
    mo sum = n, cnt = 1;
    bit.Add (b[n + 1]);
    for (int i = n - 1; i >= 0; -- i) {
        auto [s, c] = bit.Sum (a[i]);
        mo rc = cnt - c;
        W += s + rc * a[i];
        if (i >= 1) {
            bit.Add (b[i + 1]);
            ++ cnt;
            sum += b[i + 1];
        }
    }
    cnt = 1;
    for (int i = 1; i <= n; ++ i) q[i] = -1, used[i - 1] = false;
    for (int i = 1; i <= n; ++ i) if (a[i] < a[i - 1]) q[i] = a[i];
    for (int i = 1; i <= n; ++ i) if (b[i] < b[i + 1]) q[i] = b[i];
    for (int i = 1; i <= n; ++ i) if (~q[i]) used[q[i]] = true;
    vector <int> xxh;
    vector <mo> suf (n + 1, 0);
    for (int i = 0; i < n; ++ i) if (!used[i]) xxh.eb (i);
    for (int v : xxh) suf[v] = 1;
    for (int i = n - 1; i >= 0; -- i) suf[i] += suf[i + 1];
    vector <int> low;
    for (int i = 2; i < n; ++ i) {
        if (q[i] == -1) low.eb (max (a[i - 1], b[i + 1]));
    }
    sort (low.begin (), low.end (), greater <int> ());
    for (int i = 0; i < low.size (); ++ i) {
        cnt *= suf[low[i]] - i;
    }
    cout << W * cnt << endl;
}

做法 \(2\)

让我们回到

\[W=\sum_{l=0}^{n-1}\sum_{r=l+2}^{n+1}\min(a_{l},b_{r}) \]

众所周知(?),\(\min(a,b)=\frac{a+b-|a-b|}{2}\),所以

\[\begin{align*} W=&\sum_{l=0}^{n-1}\sum_{r=l+2}^{n+1}\frac{a_l+b_r-|a_l-b_r|}{2}\\ =&\frac{1}{2}\left(\sum_{l=0}^{n-1}\sum_{r=l+2}^{n+1}a_l+\sum_{l=0}^{n-1}\sum_{r=l+2}^{n+1}b_r-\sum_{l=0}^{n-1}\sum_{r=l+2}^{n+1}|a_l-a_r|\right) \end{align*} \]

考虑设 \(S(a)_i=\sum\limits_{j=0}^{i}a_j,S'(a)_i=\sum\limits_{j=0}^{i}S(a)_i\),则

\[W=\frac{1}{2}\left[nS(a)_{n-1}-\sum\limits_{l=0}^{n-1}a_l\cdot l+nS(b)_{n+1}-S'(b)_n-\sum\limits_{l=0}^{n-1}\sum\limits_{r=l+2}^{n+1}|a_l-b_r|\right] \]

其中 \(\sum\limits_{l=0}^{n-1}a_l\cdot l\) 可以 \(O(n)\) 算出。

然后,根据排列 \(q\) 的定义可以发现 \(a\) 非增 \(b\) 非降,所以后面那一坨绝对值从 \(a_l-b_r\) 变成 \(b_r-a_l\) 最多一次。

考虑对每一个 \(l\) 找到发生这个变化的分界点 \(p_l=\max\{r\mid b_r\le a_l\}\)

因为 \(a\) 不增,所以 \(p_l\) 也不增。所以我们可以用一个指针 \(p\)\(n+1\) 往左扫,找到第一个 \(b_r\le a_l\) 的位置(也就是 \(p_l\)),此时区间 \([l+2,n+1]\) 被划分成了左半边 \(b_r\le a_l\) 和右半边 \(b_r>a_l\) 的两部分,那么此时的贡献就是

\[\begin{align*} &\sum_{r=l+2}^{\min(p,n+1)}(a_l-b_r) + \sum_{r=\max(l+2,p+1)}^{n+1}(b_r-a_l) \end{align*} \]

套上刚才那个前缀和就可以 \(O(1)\) 算出。

因为整个过程 \(p\) 只降不增,所以这一部分复杂度是 \(O(n)\) 的。

于是我们在 \(O(n)\) 的时间内算出了 \(W\)。然后计算 \(q\) 的个数方法是一样的,不再多说。

Code
int n;
int a[N], b[N];
mo suma[N], sumb[N], ssum[N];
int q[N];
bool used[N];

inline void solve () {
    cin >> n;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) cin >> b[i];
    a[0] = b[n + 1] = n;
    suma[0] = a[0];
    for (int i = 1; i <= n; ++ i) suma[i] = suma[i - 1] + a[i];
    for (int i = 1; i <= n + 1; ++ i) sumb[i] = sumb[i - 1] + b[i];
    for (int i = 1; i <= n + 1; ++ i) ssum[i] = ssum[i - 1]  + sumb[i];
    mo sa = 0;
    for (int i = 0; i < n; ++ i) sa += mo(a[i]) * i;
    int p = n + 1;
    mo S = 0;
    for (int i = 0; i < n; ++ i) {
        while (p >= 2 && b[p] > a[i]) -- p;
        int ll = i + 2, rr = n + 1;
        int r1 = min (p, rr);
        if (ll <= r1) {
            S += mo(a[i]) * (r1 - ll + 1) - sumb[r1] + sumb[ll - 1];
        }
        int l1 = max (ll, p + 1);
        if (l1 <= rr) {
            S += sumb[rr] - sumb[l1 - 1] - mo(a[i]) * (rr - l1 + 1);
        }
    }
    mo W = n * suma[n - 1] - sa + n * sumb[n + 1] - ssum[n] - S;
    W /= 2;
    mo cnt = 1;
    for (int i = 1; i <= n; ++ i) q[i] = -1, used[i - 1] = false;
    for (int i = 1; i <= n; ++ i) if (a[i] < a[i - 1]) q[i] = a[i];
    for (int i = 1; i <= n; ++ i) if (b[i] < b[i + 1]) q[i] = b[i];
    for (int i = 1; i <= n; ++ i) if (~q[i]) used[q[i]] = true;
    vector <int> xxh;
    vector <mo> suf (n + 1, 0);
    for (int i = 0; i < n; ++ i) if (!used[i]) xxh.eb (i);
    for (int v : xxh) suf[v] = 1;
    for (int i = n - 1; i >= 0; -- i) suf[i] += suf[i + 1];
    vector <int> low;
    for (int i = 2; i < n; ++ i) {
        if (q[i] == -1) low.eb (max (a[i - 1], b[i + 1]));
    }
    sort (low.begin (), low.end (), greater <int> ());
    for (int i = 0; i < low.size (); ++ i) {
        cnt *= suf[low[i]] - i;
    }
    cout << W * cnt << endl;
}

第一个方法的复杂度是 \(O(n\log n)\),第二个方法的取决于你怎么排序。两个方法都进了最优解第一页。

啾啾啾~ 九鸟可爱~

posted @ 2026-07-02 11:00  XXh_Laoxu  阅读(16)  评论(0)    收藏  举报

转载请注明出处!


#页面摧毁游戏#
使用【上下左右】控制飞行器的运动
使用【空格】发射导弹
点击开始摧毁