禁书目录
题目大意:清教需要定期给Index清除记忆,在此之前需要把当中的十万三千本禁书取出来......不幸的是,禁书一旦离开了Index就非常脆弱,具体来说,每一本禁书都有一个魔力值 ai ,其记载的内容是 bi ,取出后的 n 本不同的禁书形成了一个排列,如果说对于一本禁书 i ,其左边存在一本不弱于它的魔力值的禁书 j ,禁书 i 就会因为禁书 j 的影响而消失。求对于所有可能的禁书排列,能保留下来的记载内容的种类数之和。由于答案可能很大,只需要输出对998244353 取膜后的结果即可。
数据范围:1 ≤ n ≤ 5 x 105, 1≤ ai, bi ≤ 108
题解:
这题好啊
第一道数数题转成概率期望的。
主要是,这个题数数没法数,想起来过于憋尿,我们把它变成概率期望。
假设$a$值不小于当前$a$的书有$cnt$本,那么这本书活下来的概率就是$frac{1}{cnt}$。
所以对于每一种书,算出来全都活不下来的概率,最后乘上阶乘就好了。
需要注意的是,如果出现了两本书完全相同,那么我们就只能统计其中一本因为在后面的书无论如何都会被前面的干掉。
代码:
#include <bits/stdc++.h> #define N 500010 using namespace std; typedef long long ll; const int mod = 998244353 ; map<int, int> MP; struct Node { int x, y; }a[N], b[N]; inline bool cmp(const Node &a, const Node &b) { return a.x == b.x ? a.y < b.y : a.x < b.x; } inline bool operator == (const Node &a, const Node &b) { return a.x == b.x && a.y == b.y; } char *p1, *p2, buf[100000]; #define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ ) int rd() { int x = 0; char c = nc(); while (c < 48) { c = nc(); } while (c > 47) { x = (((x << 2) + x) << 1) + (c ^ 48), c = nc(); } return x; } int qpow(int x, int y) { int ans = 1; while (y) { if (y & 1) { ans = (ll)ans * x % mod; } y >>= 1; x = (ll)x * x % mod; } return ans; } int c[N << 1], cnt, re[N << 1]; int main() { int n = rd(); int sum = 1; for (int i = 1; i <= n; i ++ ) { sum = (ll)sum * i % mod; } for (int i = 1; i <= n; i ++ ) { a[i].x = rd(), a[i].y = rd(); c[ ++ cnt] = a[i].x, c[ ++ cnt] = a[i].y; } int tot = 0; sort(c + 1, c + cnt + 1); c[0] = c[1] - 1; for (int i = 1; i <= cnt; i ++ ) { if (c[i] != c[i - 1]) { MP[c[i]] = ++tot; } } for (int i = 1; i <= n; i ++ ) { a[i].x = MP[a[i].x], a[i].y = MP[a[i].y]; } sort(a + 1, a + n + 1, cmp); int pre = 0; for (int i = 1; i <= tot; i ++ ) { re[i] = 1; } for (int i = 1; i <= n; i ++ ) { int dic = i; while (dic < n && a[dic] == a[dic + 1]) { dic ++ ; } if (a[i].x != a[pre].x) { pre = i; } int mdl = n - pre + 1; re[a[dic].y] = (ll)re[a[dic].y] * (mdl - (dic - i + 1)) % mod * qpow(mdl, mod - 2) % mod; i = dic; } int ans = 0; for (int i = 1; i <= tot; i ++ ) { ans = (ans + (1 - re[i] + mod) % mod) % mod; } cout << (ll)ans * sum % mod << endl ; return 0; }
小结:好题啊,这个题感觉很有意义的。就是如果碰见了一道数数题完全没思路(及时模数很有诱惑力),我们可以考虑转化成概率期望来做。