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=n?cnti? 。
那么如何求
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;
}
|