2024牛客暑期多校训练营1 A、B

A题

题意

0 − 2 m 0-2^m 02m的数字组成n个数的序列,这个序列需要有子序列能够AND(与)出来为 1 1 1,求序列的个数

思路

首先要想能够有数能够与出来为 1 1 1,就需要有最低位为 1 1 1的数,我们就可以将这 n n n个数进行划分,分成奇数和偶数的两部分,且奇数一定要有。假设奇数有 k k k个,先考虑简单的偶数,偶数只需要最底下为 0 0 0即可,所以偶数的个数为 ( 2 ( n − k ) ) ( m − 1 ) {(2^{(n-k)})}^{(m-1)} (2(nk))(m1),即 2 ( n − k ) ( m − 1 ) {2^{(n-k)(m-1)}} 2(nk)(m1)种,而奇数每行都要至少有一个 0 0 0,所以要排除一行全为 1 1 1的情况,所以奇数个数有 ( 2 k − 1 ) ( m − 1 ) {(2^{k}-1)}^{(m-1)} (2k1)(m1)种,又因为 k k k个奇数是随便选取的,有 ( n k ) n \choose k (kn)种,将三者相乘即可,为 2 ( n − k ) ( m − 1 ) ⋅ ( 2 k − 1 ) ( m − 1 ) ⋅ ( n k ) {2^{(n-k)(m-1)}}\cdot{(2^{k}-1)}^{(m-1)}\cdot{n \choose k} 2(nk)(m1)(2k1)(m1)(kn)可以用直接求解,也可以用快速幂优化,组合数用 O ( n 2 ) O(n^2) O(n2)预处理即可,总共时间复杂度为 O ( n 2 ) O(n^2) O(n2)
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;

const int N = 5100;

using LL = long long;

LL n, m, p;
LL C[N][N], P2[N];

void init(int n){
    
    for(int i = 0; i <= n; i ++){
        for(int j = 0; j <= i; j ++){
            if(!j) C[i][j] = 1;
            else C[i][j] = (C[i-1][j-1] + C[i-1][j]) % p;
        }
    }
    P2[0] = 1;
    for(int i = 1; i <= n; i ++) P2[i] = P2[i-1] * 2LL % p; 
}

LL qmi(LL a, LL b, LL p){
    LL res = 1;
    while(b){
        if(b&1) res = res * a % p;
        b >>= 1; 
        a = a * a % p;
    }
    return res;
}

int main(){

    cin >> n >> m >> p;

    init(N - 1);

    LL res = 0;

    for(int i = 1; i <= n; i ++){
        int res1 = qmi(P2[i] - 1, m - 1, p);
        int res0 = qmi(P2[(n-i)], m - 1, p);
        res = (res + 1LL * res1 * res0 % p * C[n][i] % p)%p;
    }
    cout << res << "\n";

    return 0;
}

B题

题意

和A题基本一样,只是需要找到两个子序列满足 A N D ( 与 ) AND(与) AND() 1 1 1

思路

从A题我们找到了至少有一个子序列满足的情况,那么两个子序列是不是只要再减去只有一个子序列的种数即可,我们还是分成偶数和奇数两部分,如果是偶数,和 A A A题一样也为 2 ( n − k ) ( m − 1 ) {2^{(n-k)(m-1)}} 2(nk)(m1),对于奇数,由于 k = 1 k=1 k=1时全部都不满足,所以我们可以直接从 k = 2 k=2 k=2开始枚举,而 k ≥ 2 k \ge 2 k2时只有当 k k k个数中二进制的 m − 1 m-1 m1位中每一位都只有唯一的 0 0 0才会导致你删除了一个数之后就会 A N D AND AND不等于 1 1 1,我们叫这一位为特殊位,举个例子
1 1 0
1 0 1
0 1 1
1 1 1
这三个数中因为每一位都有一个特殊位,导致去掉一位之后 A N D AND AND值为 1 1 1
然后我们就需要计算特殊位的个数,我们定义 f k , j f_{k,j} fk,j k k k个数中有 j j j个“特殊位”的方案数。我们利用 d p dp dp的思想去求解这个方案数,这个可以从 j − 1 j-1 j1个特殊位推,可以是本来就有 k k k个数,每一个数都可以贡献出 1 1 1个特殊位,也可以是 k − 1 k-1 k1个数,可以是新加的数贡献 1 1 1个特殊位,也可以是原来的 k − 1 k-1 k1个数贡献,新加的一个数只是作为普通值,所以可以推出式子
f k , j = k ∗ ( f k − 1 , j − 1 + f k , j − 1 ) f_{k,j} = k*(f_{k-1,j-1}+f_{k,j-1}) fk,j=k(fk1,j1+fk,j1)
其他位除了不能全为 1 1 1,还要排除只有 1 1 1个0的情况,即为 ( 2 k − k − 1 ) m − 1 − j {(2^k-k-1)}^{m - 1 - j} (2kk1)m1j,j为特殊位的个数,还要计算从 m − 1 m-1 m1位中挑出 j j j个特殊位,为 ( m − 1 j ) {m-1 \choose j} (jm1)
所以k个数的方案数为其相加即为 ∑ j = k m − 1 ( ( m − 1 j ) ⋅ d p k , j ⋅ ( 2 k − k − 1 ) m − 1 − j ) \sum^{m-1}_{j=k} ({m-1 \choose j} \cdot dp_{k,j} \cdot {(2^{k}-k-1)}^{m - 1 - j}) j=km1((jm1)dpk,j(2kk1)m1j)

所以每次答案为 2 ( n − k ) ( m − 1 ) ⋅ ( 2 k − 1 ) ( m − 1 ) ⋅ ( n k ) − ∑ j = k m − 1 ( ( m − 1 j ) ⋅ d p k , j ⋅ ( 2 k − k − 1 ) m − 1 − j ) {2^{(n-k)(m-1)}}\cdot{(2^{k}-1)}^{(m-1)}\cdot{n \choose k} - \sum^{m-1}_{j=k} ({m-1 \choose j} \cdot dp_{k,j} \cdot {(2^{k}-k-1)}^{m - 1 - j}) 2(nk)(m1)(2k1)(m1)(kn)j=km1((jm1)dpk,j(2kk1)m1j)
k k k 2 − n 2-n 2n累加即可。

#include <bits/stdc++.h>

using namespace std;

const int N = 5100;

using LL = long long;

LL n, m, p;
LL f[N][N];
LL dp[N][N], P2[N];

void init(int n){
    P2[0] = 1;
    for(int i = 1; i <= n; i ++) P2[i] = P2[i-1] * 2LL % p;
    for(int i = 0; i <= n; i ++){
        for(int j = 0; j <= i; j ++){
            if(!j) f[i][j] = 1%p;
            else f[i][j] = (f[i-1][j-1] + f[i-1][j]) % p;
        }
    }
}

LL qmi(LL a, LL b, LL p){
    LL res = 1;
    while(b){
        if(b&1) res = res * a % p;
        b >>= 1; 
        a = a * a % p;
    }
    return res;
}

LL work()
{
    dp[0][0] = 1;
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m - 1; j ++){
            dp[i][j] = (i * (dp[i][j-1] + dp[i-1][j-1])%p)%p;
        }
    }
    LL res = 0;
    
    for(int i = 2; i <= n; i ++){
        LL ans0 = 1;
        for(int j = 1; j <= m - 1; j ++){
            ans0 = (ans0 * (P2[n-i])%p)%p;
        }
        LL cur = 1, ans1 = 0;
        for(int j = m - 1; j >= i; j --){
            ans1 = (ans1 + f[m-1][j] * dp[i][j] %p * cur % p)%p;
            cur = (cur*((P2[i]-1-i)%p)%p) % p;   
            
        }
        //cout << ans0 <<  "\n";
        res = (res + ans0 * ans1%p * f[n][i]%p) % p;
    }
    
    return res;
}

int main(){

    cin >> n >> m >> p;

    init(N - 1);

    LL res = 0;

    for(int i = 2; i <= n; i ++)
    {
        res = (res + f[n][i] * (qmi(qmi(2, i % p, p) - 1, m - 1, p)) % p * qmi(qmi(2, n - i, p), m - 1, p)%p) % p;
    }

    res = res - work();

    cout << (res%p+p)%p << "\n";

    return 0;
}
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值