Codeforces Round #775 (Div. 2) E.Tyler and Strings

Problem - E - Codeforces

题意:给你两个数组s、t,让你重新排列s的数,问有多少种排列使得s的字典序严格小于t。s,t的长度和数组内的数开到2e5。

首先先得知道多重集的排列数:

一个由c1个a1,c2个a2……ck个ak组成的多重集,全排列个数为:

P(c1,c2,…,cK)=(c1+c2+…+cK)! / (c1!⋅c2!⋅…⋅cK!)

最直接的想法就是:枚举i表示s与t的最长相同前缀长度为i,再枚举满足限制的s[i+1](s[i+1]<t[i+1]),之后的数全排列,将所有情况数求和即可。但是复杂度过大,因此思考如何优化。

假设add_i表示当前位置放置了i,之后的全排列数个数。显然可以得到这个公式
add_i = \frac{(cnt1+cnt+...+cnt_{K-1})!}{cnt1!*cnt2*...*(cnt_{i}-1)!*...*cntK!}

这个值有一个好处就是,如果可以知道这个值如何递推,那么就可以通过求前缀来得到贡献。

到下一位,总的可选择数字少了一个,对应数字i的总数也少了一个,因此通过乘上一个\frac{cnt_i}{cnt_1+cnt_2+...+cnt_{K-1}}即可。但是要特判当前填的数与t的上一位数字相同的情况,因为cnt_i的值最初就比别的数少1,因此应该乘\frac{cnt_i-1}{cnt_1+cnt_2+...+cnt_{K-1}},可以通过全乘上前者,再在这个值上乘\frac{cnt_i-1}{cnt_i}

修改操作是两次区间乘法,计算结果是通过前缀和,因此可以通过线段树或者树状数组实现。

实现上注意两个地方,一个是实际情况cnt不能为负,所以cnt为负直接break掉。第二是考虑到字典序的特性,要特判n<m且s为t的前缀的情况。

题目很良心地没有卡常,基本逻辑对了就能过。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int M = 2e5 + 10;
int tl[M << 2], tr[M << 2], lz[M << 2], val[M << 2], ipt[M];
int cnt[M], t[M], fac[M], inv[M], n, m, ini;
int qpow(int a,int p){
    int ans = 1;
    while(p){
        if(p & 1)ans = ans * a % mod;
        a = a * a % mod;
        p >>= 1;
    }
    return ans;
}
void init(int n){
    fac[0] = 1;
    for(int i=1;i<=n;++i){
        fac[i] = fac[i-1] * i % mod;
    }
    inv[n] = qpow(fac[n],mod - 2);
    for(int i=n-1;i>0;--i){
        inv[i] = inv[i + 1] * (i + 1) % mod;
    }
    inv[0] = 1;
}
inline void pushup(int i) {
    val[i] = (val[i << 1] + val[i << 1 | 1]) % mod;
}
inline void pushdown(int i) {
    if (lz[i] != 1) {
        int ls = i << 1, rs = i << 1 | 1;
        val[ls] = val[ls] * lz[i] % mod; val[rs] = val[rs] * lz[i] % mod;
        lz[ls] = lz[ls] * lz[i] % mod; lz[rs] = lz[rs] * lz[i] % mod;
        lz[i] = 1;
    }
}
inline void build(int i, int l, int r) {
    tl[i] = l; tr[i] = r; lz[i] = 1;
    if (l == r) { val[i] = ipt[l]; return; }
    int mid = (l + r) >> 1; build(i << 1, l, mid); build(i << 1 | 1, mid + 1, r);
    pushup(i);
}
inline int calc(int i, int l, int r) {
    if (tl[i] >= l && tr[i] <= r) return val[i]; if (tr[i] < l || tl[i] > r) return 0;
    pushdown(i);
    int sum = 0;
    if (tr[i << 1] >= l) sum = (sum + calc(i << 1, l, r))%mod; if (tl[i << 1 | 1] <= r)sum = (sum + calc(i << 1 | 1, l, r))%mod;
    return sum;
}
inline void mul(int i, int l, int r, int num) {
    if (tl[i] >= l && tr[i] <= r) {val[i] = val[i] * num % mod; lz[i] = lz[i] * num % mod; return;}
    pushdown(i);
    if (tr[i << 1] >= l) mul(i << 1, l, r, num); if (tl[i << 1 | 1] <= r)mul(i << 1 | 1, l, r, num);
    pushup(i);
}
signed main(){
    init(200000);
    cin >> n >> m;
    for(int i=1;i<=n;++i){
        int tmp;cin>>tmp;
        cnt[tmp]++;
    }
    for(int i=1;i<=m;++i){
        cin>>t[i];
    }

    int mm = min(n,m);
    ini = fac[n - 1];
    for(int i=1;i<=200000;++i){
        if(!cnt[i])continue;
        ini = ini * inv[cnt[i]] % mod;
    }
    for(int i=1;i<=200000;++i){
        if(!cnt[i])continue;
        ipt[i] = ini * cnt[i] % mod;
    }
    build(1,1,200000);

    int ans = 0, cntt = n - 1, check = 0;
    for(int i=1;i<=mm;++i){
        if(t[i] != 1){
            ans = (ans + calc(1,1,t[i] - 1)) % mod;
        }
        if(i == mm){
            check = 1;
            break;
        }
        if(cnt[t[i]] == 0)break;
        mul(1,1,200000,cnt[t[i]] * qpow(cntt,mod-2) % mod);
        mul(1,t[i],t[i],(cnt[t[i]] - 1) * qpow(cnt[t[i]], mod-2) % mod);
        cntt--;
        cnt[t[i]]--;
    }
    if(check && n < m){//特判前缀完全相同
        ans = (ans + calc(1,t[n],t[n])) % mod;
    }
    cout<<ans<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值