Codeforces Round #775

Tyler and Strings

题意:给你一个序列长度为 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 i1的位置,则当前的 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!} ((cnt11)!cnt2!cnt3!cntk!(ni)!+(cnt1!(cnt21)!cnt3!cntk!(ni)!++(cnt1!cnt2!cnt3!(cnti11)!cntk!(ni)!。观察式子可以化成 ( 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!(ni)!cnt1+(cnt1!cnt2!cnt3!cntk!(ni)!cnt2++(cnt1!cnt2!cnt3!cnti1!cntk!(ni)!cnti1。故设 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!(ni)!,选取当前位的总贡献为 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++cnti1)。所以使用树状数组维护前缀和。
然后有一种唯一没考虑的情况就是 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值