C
组合数学,1900,数状数组
题意
给出 s , t s, t s,t 两个序列,长度分别为 n , m n,m n,m,问 s s s 有多少种排序方式满足 s s s 的字典序比 t t t 小。 1 ≤ n , m ≤ 2 e 5 1\leq n,m\leq 2e5 1≤n,m≤2e5
思路
因为是按字典序比较,所以如果 s 1 < t 1 s_1<t_1 s1<t1 之后的就可以随便排了,所以考虑枚举前 i − 1 i-1 i−1 位相同且 s i < t i s_i<t_i si<ti 的排序方式有 c n t i cnt_i cnti 种,最后的答案就是 ∑ i = 1 i = n c n t i \sum_{i=1}^{i=n} cnt_i ∑i=1i=ncnti 。
那么如何求 c n t i cnt_i cnti 呢?如果不考虑可重复排列,那么 c n t i = n o w × ( n − i ) ! cnt_i= now \times (n-i)! cnti=now×(n−i)! ,即到第 i 位当前满足小于 t i t_i ti 的个数乘上之后数的全排列(因为 i i i 后面的数可以随便取了)。 n o w now now 可以直接用数状数组维护前缀和(因为前 i − 1 i-1 i−1 位两序列相同,所以可以直接将那个数字数量减1)。于是问题重点在于加上可重复排列后怎么做。
首先可重复排列要在
n
!
n!
n! 的基础上除以每个元素出现数量的阶乘。即当我们遍历到第
i
i
i 位时,要在
(
n
−
i
)
!
(n-i)!
(n−i)! 的基础上除以
c
n
t
1
!
×
c
n
t
2
!
×
c
n
t
i
!
.
.
.
.
×
c
n
t
n
!
{cnt_1!\times cnt_2!\times cnt_i!....\times cnt_n!}
cnt1!×cnt2!×cnti!....×cntn!
其中
c
n
t
1
cnt_1
cnt1 是减去前
i
−
1
i-1
i−1 位数字后的对应数字的数量。然后我们就可以先预处理出
i
=
0
i=0
i=0 时的上式记为 fenzi
,由
1
c
n
t
1
!
×
c
n
t
2
!
×
(
c
n
t
i
−
1
)
!
.
.
.
.
×
c
n
t
n
!
=
1
c
n
t
1
!
×
c
n
t
2
!
×
c
n
t
i
!
.
.
.
.
×
c
n
t
n
!
×
c
n
t
i
\frac{1}{cnt_1!\times cnt_2!\times (cnt_i-1)!....\times cnt_n!}=\frac{1}{cnt_1!\times cnt_2!\times cnt_i!....\times cnt_n!}\times cnt_i
cnt1!×cnt2!×(cnti−1)!....×cntn!1=cnt1!×cnt2!×cnti!....×cntn!1×cnti,只需每次遍历后将 fenzi *= cnt[t[i]]
并更新 cnt[t[i]]
即可。
ps.不要忘记考虑
s
s
s 可能恰好为
t
t
t 的前缀的情况,开始忘了,还好有样例1
代码
void solve() {
cin >> n >> m;
fac[0] = 1;
for(int i = 1; i <= n; i++) {
fac[i] = (fac[i-1] * i) % P;
}
for(int i = 1; i <= n; i++) {
cin >> s[i];
update(s[i], 1);
cnt[s[i]]++;
}
for(int i = 1; i <= m; i++) {
cin >> t[i];
}
int ans = 0;
int mi = min(n, m);
int fz = 1;
for(int i = 1; i <= 2e5; i++) {
if(cnt[i]) {
fz = fz * fac[cnt[i]] % P;
}
}
fz = power(fz, P - 2);
int flag = 1;
if(n >= m) {
flag = 0;
}
for(int i = 1; i <= mi; i++) {
int p = ask(t[i] - 1);
ans = (ans + p * fac[n-i] % P * fz % P) % P;
if(!cnt[t[i]]) {
flag = 0;
break;
}
fz = fz * cnt[t[i]] % P;
cnt[t[i]]--;
update(t[i], -1);
}
ans += flag;
ans %= P;
cout << ans << endl;
}