题意:给你一个序列长度为
n
n
n的
a
a
a和长度为
m
m
m的
b
b
b,让你对
a
a
a进行排列,使得
a
a
a字典序小于
b
b
b。输出方案数
思路:首先,对于一个长度为n的序列,设数组
c
n
t
i
cnt_i
cnti记录其中数字i的数量,则其全排列数量是
n
!
c
n
t
1
!
c
n
t
2
!
c
n
t
3
!
⋯
c
n
t
k
!
\frac {n!}{cnt_1!cnt_2!cnt_3!\cdots cnt_k!}
cnt1!cnt2!cnt3!⋯cntk!n! ,
k
k
k是其中出现的最大数字。为了使序列a小于序列b,首先让他们拥有相同的前缀,让我们迭代从可能的长度为
0
0
0到
m
i
n
(
n
,
m
)
min(n,m)
min(n,m)的相同前缀,然后在下一位填比序列
b
b
b小的数字,之后的数字任意排列。假设当前迭代到了相同的前缀
i
−
1
i-1
i−1的位置,则当前的
i
i
i位置可以选择
b
[
i
]
−
1
b[i]-1
b[i]−1种可能,注意此时所有的
c
n
t
cnt
cnt保存的是剩下的数字数量,这时所有贡献为
(
n
−
i
)
!
(
(
c
n
t
1
−
1
)
!
c
n
t
2
!
c
n
t
3
!
⋯
c
n
t
k
!
+
(
n
−
i
)
!
(
c
n
t
1
!
(
c
n
t
2
−
1
)
!
c
n
t
3
!
⋯
c
n
t
k
!
+
⋯
+
(
n
−
i
)
!
(
c
n
t
1
!
c
n
t
2
!
c
n
t
3
!
⋯
(
c
n
t
i
−
1
−
1
)
!
⋯
c
n
t
k
!
\frac {(n - i)!}{((cnt_1 - 1)!cnt_2!cnt_3!\cdots cnt_k!} + \frac {(n - i)!}{(cnt_1!(cnt_2 - 1)!cnt_3!\cdots cnt_k!} + \cdots + \frac {(n - i)!}{(cnt_1!cnt_2!cnt_3!\cdots (cnt_{i-1}-1)! \cdots cnt_k!}
((cnt1−1)!cnt2!cnt3!⋯cntk!(n−i)!+(cnt1!(cnt2−1)!cnt3!⋯cntk!(n−i)!+⋯+(cnt1!cnt2!cnt3!⋯(cnti−1−1)!⋯cntk!(n−i)!。观察式子可以化成
(
n
−
i
)
!
(
c
n
t
1
!
c
n
t
2
!
c
n
t
3
!
⋯
c
n
t
k
!
⋅
c
n
t
1
+
(
n
−
i
)
!
(
c
n
t
1
!
c
n
t
2
!
c
n
t
3
!
⋯
c
n
t
k
!
⋅
c
n
t
2
+
⋯
+
(
n
−
i
)
!
(
c
n
t
1
!
c
n
t
2
!
c
n
t
3
!
⋯
c
n
t
i
−
1
!
⋯
c
n
t
k
!
⋅
c
n
t
i
−
1
\frac {(n - i)!}{(cnt_1!cnt_2!cnt_3!\cdots cnt_k!} \cdot cnt_1+ \frac {(n - i)!}{(cnt_1!cnt_2!cnt_3!\cdots cnt_k!} \cdot cnt_2 + \cdots + \frac {(n - i)!}{(cnt_1!cnt_2!cnt_3!\cdots cnt_{i-1}! \cdots cnt_k!} \cdot cnt_{i-1}
(cnt1!cnt2!cnt3!⋯cntk!(n−i)!⋅cnt1+(cnt1!cnt2!cnt3!⋯cntk!(n−i)!⋅cnt2+⋯+(cnt1!cnt2!cnt3!⋯cnti−1!⋯cntk!(n−i)!⋅cnti−1。故设
n
o
w
=
(
n
−
i
)
!
c
n
t
1
!
c
n
t
2
!
c
n
t
3
!
⋯
c
n
t
k
!
now = \frac {(n - i)!}{cnt_1!cnt_2!cnt_3!\cdots cnt_k!}
now=cnt1!cnt2!cnt3!⋯cntk!(n−i)!,选取当前位的总贡献为
n
o
w
⋅
(
c
n
t
1
+
c
n
t
2
+
⋯
+
c
n
t
i
−
1
)
now\cdot (cnt_1 + cnt_2 + \cdots + cnt_{i-1})
now⋅(cnt1+cnt2+⋯+cnti−1)。所以使用树状数组维护前缀和。
然后有一种唯一没考虑的情况就是
a
a
a比
b
b
b短能成为b的前缀,我们只需要单独判断这种情况然后加1就可以了。
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
constexpr int N = 200000 + 10, MOD = 998244353;
ll fact[N], inv[N];
ll ksm(ll a, ll k)
{
ll ans = 1;
while(k)
{
if(k & 1)
ans = ans * a % MOD;
a = a * a % MOD;
k >>= 1;
}
return ans;
}
template <typename T>
struct Fenwick {//树状数组
const int n;
vector<T> a;
Fenwick(int n) : n(n), a(n + 1) {}
void add(int x, T v) {
for (int i = x; i <= n; i += i & -i) {
a[i] += v;
}
}
T sum(int x) {
T ans = 0;
for (int i = x; i > 0; i -= i & -i) {
ans += a[i];
}
return ans;
}
T rangeSum(int l, int r) {
return sum(r) - sum(l - 1);
}
};
void init()//预处理好阶乘的逆元
{
fact[0] = inv[0] = 1;
for(int i = 1; i <= N - 10; i ++)
fact[i] = fact[i - 1] * i % MOD;
inv[N - 10] = ksm(fact[N - 10], MOD - 2);
for(int i = N - 11; i >= 1; i --)
inv[i] = inv[i + 1] * (i + 1) % MOD;
}
void solve()
{
int n, m;
cin >> n >> m;
vector<int> a(n + 1), b(m + 1), cnt(N);//cnt是计算所有的数字出现次数 需要开到值域范围
Fenwick <int> tr(N);//用树状数组维护前缀和
for(int i = 1; i <= n; i ++)
{
cin >> a[i];//读入a数组
cnt[a[i]] ++;//记录数字出现次数
tr.add(a[i], 1);
}
for(int i = 1; i <= m; i ++)
cin >> b[i];
ll NowInv = 1;
for(int i = 1; i <= N - 10; i ++)
NowInv = NowInv * inv[cnt[i]] % MOD;//还没选的时候的分母先算出来
ll ans = 0;
ll exAdd = (n < m);//如果n>=m,一定不存在a是b的前缀的可能
for(int i = 1; i <= n; i ++)
{
if(i > m)
break;
ans += fact[n - i] * tr.sum(b[i] - 1) % MOD * NowInv % MOD;//计算当前位的贡献
if(!cnt[b[i]])//如果当前位置不能成为b的前缀,后面无论怎么排列a<b的情况都已经被计算了,所以直接退出
{
exAdd = 0;
break;
}
NowInv = NowInv * cnt[b[i]] % MOD;//更新新的分母
cnt[b[i]] --;//选择b[i]作为i当前的数字,继续计算下一位的贡献
tr.add(b[i], -1);//维护树状数组
}
ans = (ans + exAdd) % MOD;
cout << ans << "\n";
}
int main()
{
init();
solve();
return 0;
}