组合数学习题集

一.E - NEQ (atcoder.jp)

       (1)题目大意

        给定你两个数n,m,在m个数中选择n个数组成A序列,从m中选择n个数再组成一个B序列。

现在问你满足Ai ≠ Bi的并且Ai ≠ Aj的序列总数有多少?

         (2)解题思路

                我们先把一个序列固定,对于另一个序列,我们可以利用容斥原理容易解的,对于我们有0个相同的是我们刚开始必然的答案数,若是有1个相同的那么我们就需要减去,但是我们就会减去多的有两个相同的方案,因此我们需要加上有两个相同的方案,因此奇数减偶数加。最后我们的第一个序列的总数是C(m,n),因此答案就出来了。

         (3)代码实现

#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int N = 1010000,mod = 1e9 + 7;
ll fac[N],inv[N];
ll C(int a,int b)
{
    if(a < b) return 0;
    return fac[a] * inv[b] % mod * inv[a - b] % mod;
}
ll P(int a,int b)
{
    if(a < b) return 0;
    return fac[a] * inv[a - b] % mod;;
}
void exgcd(int a,int b,ll &x,ll &y)
{
    if(!b) {
        x = 1,y = 0;
        return ;
    }
    exgcd(b,a % b,y,x);
    y -= a / b * x;
}
void init(int n)
{   
    ll x,y;
    fac[0] = inv[0] = 1;
    for(int i = 1;i <= n;i++) fac[i] = i * fac[i - 1] % mod;
    exgcd(fac[n],mod,inv[n],y);
    inv[n] = (inv[n] % mod + mod) % mod;
    for(int i = n - 1;i >= 1;i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
int main()
{
    init(1e6);
    int n,m;
    cin >> n >> m;
    ll ans = 0,p = 1;
    for(int i = 0;i <= n;i++) {
        ans = (ans + p * C(n,i) * P(m - i,n - i) % mod) % mod;
        p = -p;
    }
    cout << P(m,n) * ((ans + mod) % mod) % mod << endl;
    return 0;
}

二.F - Reordering (atcoder.jp)

        (1)题目大意

                给定的是一个字符串S,有多少不同的字符串可以作为一个非空的,不一定是连续的S子序列的排列?因为计数可能非常大,所以按998244353进行模数打印。

         (2)解题思路

                这个题考虑进行组合数预处理,然后进行dp,dp[i][j],表示有i类字符,选j个的方案数。

那么我们把这个字符串的每个字符进行预处理,然后从0-25,依次处理。

                状态转移就是:dp[i + 1][j + k] = (dp[i + 1][j + k] + dp[i][j] * C[j + k][k] % mod) % mod

                从i+1类字符中选j+k个可以从第i类字符中选j个*从j+k中选k个的方案数,j是前面字符的个数。

        (3)代码实现

#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const int N = 5e3 + 10;
const int mod = 998244353;
ll dp[30][N], C[N][N];
int cnt[N];
void init()
{
    C[0][0] = 1;
    for (int i = 1; i <= 5000; i++)
    {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++)
        {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
        }
    }
}
void solve()
{
    string s;
    cin >> s;
    for (int i = 0; i < s.size(); i++)
    {
        cnt[s[i] - 'a']++;
    }
    dp[0][0] = 1;
    ll ans = 0, sum = 0;
    for (int i = 0; i < 26; i++)
    {
        for (int j = 0; j <= sum; j++)
        {
            for (int k = 0; k <= cnt[i]; k++)
            {
                dp[i + 1][j + k] = (dp[i + 1][j + k] + dp[i][j] * C[j + k][k] % mod) % mod;
            }
        }
        sum += cnt[i];
        sum %= mod;
    }
    for (int i = 1; i <= s.size(); i++)
    {
        ans = (ans + dp[26][i]) % mod;
    }
    cout << ans << endl;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    init();
    int T = 1;
    // cin >> T;
    while (T--)
        solve();
    return 0;
}

三.E - LEQ (atcoder.jp)

        (1)题目大意

        给定N个整数序列:a = (A1, A2,…一个)。找出(不一定是连续的)子序列A' = (A1', A2',…), Ak')长度至少2,满足以下条件:A1' <= Ak'由于计数可能非常大,按998244353的模量打印。在这里,当两个子序列来自不同的索引集时,即使它们与序列相同,也要区分它们。

         (2)解题思路

        对于第i个数,我们查询前面所有比他小的都能构成方案,然后这两个数中间的数随便选,显然是个二项式系数,最后方案为2^len,但是我们不容易维护出这个前面比他小的数量能够O1算答案,因此我们考虑离散化A数组,对于每一个位置我们都插入树状数组2的i次方的逆元,然后我们查询前面有多少比他小的就会把这些逆元加上来,然后*2^i次方,然后除以2即可。

        (3)代码实现

// Problem: E - LEQ
// Contest: AtCoder - AtCoder Beginner Contest 221
// URL: https://atcoder.jp/contests/abc221/tasks/abc221_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include "bits/stdc++.h"
#define rep(i, z, n) for (ll i = z; i <= n; i++)
#define per(i, n, z) for (ll i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<ll, ll>
#define fi first
#define se second
#define vi vector<ll>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
using namespace std;
const ll mod = 998244353;
const ll N = 3e5 + 10;
ll pw[N], inv[N], a[N], seg[N], m;
ll ksm(ll a, ll p)
{
    ll res = 1;
    while (p)
    {
        if (p & 1)
            res = 1LL * res * a % mod;
        a = 1LL * a * a % mod;
        p >>= 1;
    }
    return res;
}
ll lowbit(ll x)
{
    return x & -x;
}
void add(ll x, ll v)
{
    while (x <= m)
    {
        seg[x] = (seg[x] + v) % mod;
        x += lowbit(x);
    }
}
ll qry(ll x)
{
    ll res = 0;
    while (x >= 1)
    {
        res = (res + seg[x]) % mod;
        x -= lowbit(x);
    }
    return res;
}
void init()
{
    ll n = 3e5;
    pw[0] = inv[0] = 1;
    for (ll i = 1; i <= n; i++)
    {
        pw[i] = pw[i - 1] * 2 % mod;
    }
    inv[n] = ksm(pw[n], mod - 2);
    for (ll i = n - 1; i >= 1; i--)
    {
        inv[i] = 2 * inv[i + 1] % mod;
    }
}
vector<ll> v;
void solve()
{
    ll n;
    cin >> n;
    for (ll i = 1; i <= n; i++)
    {
        cin >> a[i];
        v.push_back(a[i]);
    }
    sort(v.begin(), v.end());
    m = v.size();
    ll ans = 0;
    for (ll i = 1; i <= n; i++)
    {
        ll idx = lower_bound(v.begin(), v.end(), a[i]) - v.begin();
        ans = (ans + 1LL * qry(idx + 1) * pw[i] % mod * inv[1] % mod) % mod;
        add(idx + 1, inv[i]);
    }
    cout << ans << endl;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    init();
    ll T = 1;
    // cin >> T;
    while (T--)
        solve();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值