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\)。所以我们考虑将题目中的和式转换一下:
注意到
所以
其中集合 \(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\) 个位置的候选数量为
因为前面 \(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\)
让我们回到
众所周知(?),\(\min(a,b)=\frac{a+b-|a-b|}{2}\),所以
考虑设 \(S(a)_i=\sum\limits_{j=0}^{i}a_j,S'(a)_i=\sum\limits_{j=0}^{i}S(a)_i\),则
其中 \(\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\) 的两部分,那么此时的贡献就是
套上刚才那个前缀和就可以 \(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)\),第二个方法的取决于你怎么排序。两个方法都进了最优解第一页。
啾啾啾~ 九鸟可爱~

浙公网安备 33010602011771号