salute-to-Mr-Lynch

导航

2024年牛客暑期多校训练营1 A题 A Bit Common题解 和 B-A Bit More Common题解

A题A Bit Common题目的大意:

  • 首先,给你一个长度为n的序列A,A序列中每一个元素全都小于2m,并且大于等于0。
  • A序列要满足存在一个非空子序列的与运算(&)和为1;
  • 输出这样的A序列有几个,最后对正整数q取模。(1 <= n , m <=5000 , 1 <= q <=109)
  • 输入只有一行n , m , q,输出包含一个整数。

 

题解:

  要满足A序列的一个子序列 & 运算和为 1,则至少存在一个奇数。设A序列中存在k个整数,有n-k个偶数。

  下图将每个数转换为2进制之后的 (m x k) 图 ,图中每一列都是一个数。

 


在这个图中每一行一共有2k种,去除全是1的情况(因为在取子序列时所有数全是奇数,要保证最后&和的结果是1,则每一行至少有一个0)有2k-1种,然后一共有m-1行,那么奇数总共有(2k-1)m-1种情况。

同理可得,偶数有2(n-k)(m-1)种情况。

又因为实际情况中奇数和偶数都是随机的,则总共有Ck2(n-k)(m-1) · (2k-1)m-1种情况。最后再从1到n进行累加得 ∑nk=1Ck2(n-k)(m-1) · (2k-1)m-1

 

代码编写:

  写代码的过程中主要有两个难点。一是求幂,因为k可以取到5000的,25000已经超过了long long的数据范围,我的解决方法是快速幂。二是求组合数,这里我采用了杨辉三角求组合数。

  先讲一下快速幂。举个例子,求310,它可以变成95 。 这里5是奇数,我们将它变成 9 x 94,继续降低它的幂,变成9 x 812,最后变成 9 x (812)1

  在这个过程中,我们实际计算了(3*3)、(9*9)、(81*81)和 (9*812),我们需要计算10次,现在只需要计算4次,应用快速幂我们可以将O(n)的算法优化到O(logn)。

ll fast_POW(ll a ,ll b,ll c) //a是底数,b是指数,c是模
{
    ll ans=1;
    while(b)
    {
        if(b&1)//判断b是否为奇数
     { ans
=(ans*a)%c; } a=(a*a)%c; b>>=1;//指数减小一倍 } return ans; }

  然后再讲一下杨辉三角求组合数

  首先Cnm=Cnm-1+Cn-1m-1,取第m个数时可以分两种考虑取或者不取,如果取则从剩下的m-1个数中取n-1个数,如果不取则从剩下的m-1个数中取n个数。

  那么就可以通过递推公式dp[ i ][ j ]=dp[ i-1 ][ j ] + dp[ i-1 ][ j-1]。 

 

i \ j 0 1 2 3 4 5 6 7 ....
0 1                
1 1 1              
2 1 2 1            
3 1 3 3 1          
4 1 4 6 4 1        
5 1 5 10 10 5 1      
6 1 6 15 20 15 6 1    
7 1 7 21 35 35 21 7 1  
....                 1
typedef long long ll;
ll combination[5005][5005];

   int q=1e9;
    for(int i=0;i<=5001;i++)
    {
        combination[i][0]=1;
        combination[i][i]=1;
    }
    for(int i=1;i<=5001;i++)
    {
        for(int j=1;j<=i;j++)
        {
            combination[i][j]=(combination[i-1][j]%q+combination[i-1][j-1]%q)%q;//为了防止最后的值超出限制,要进行取模。
        }
    } 

  完整代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

ll combination[5005][5005];

ll fast_POW(ll a ,ll b,ll c)//算法时间复杂度为O(logN)
{
    ll ans=1;
    while(b)
    {
        if(b&1){
            ans=(ans*a)%c;
        }
        a=(a*a)%c;
        b>>=1;
    }
    return ans;
}

inline ll comb(ll n, ll m)
{
    return combination[n][m];
 } 
void solve(ll n ,ll m , ll q)
{
    for(int i=0;i<=5001;i++)
    {
        combination[i][0]=1;
        combination[i][i]=1;
    }
    for(int i=1;i<=5001;i++)//递推算法时间复杂度为O(N2)
    {
        for(int j=1;j<=i;j++)
        {
            combination[i][j]=(combination[i-1][j] % q +combination[i-1][j-1] %q)%q;//一定要注意取模,每一个可能超出模q的运算都要取模,不然会非常折磨。
        }
    } 
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ll ji=fast_POW(2, i ,q)-1;
        ans += comb(n , i)  * fast_POW(ji,m-1,q) %q * fast_POW(fast_POW(2,n-i,q),m-1,q);//一定要注意取模,每一次可能超出模q的运算都要取模,很折磨。
        ans=ans%q; 
    }
    cout << ans;
}


signed main()
{
    ll n,m,q;
    cin >> n >> m >> q;
    solve(n,m,q);
    return 0;
}

 

B题A Bit More Common题目的大意:

  • 首先,给你一个长度为n的序列A,A序列中每一个元素全都小于2m,并且大于等于0。
  • A序列要满足存在两个非空子序列的与运算(&)和为1;
  • 输出这样的A序列有几个,最后对正整数q取模。(1 <= n , m <=5000 , 1 <= q <=109)
  • 输入只有一行n , m , q,输出包含一个整数。

题解:

  这题在A题的基础上做了一些改动,将满足存在一个非空子序列的与运算(&)和为1改为存在两个非空子序列的与运算(&)和为1。那么只需要在A题的基础上减去有且仅有一个非空子序列的(&)运算和为1的情况即可。现在考虑什               么情况下,一个序列有且仅有一个非空子序列。当删去满足该条件的子序列中任意一个数之后,该子序列都不再满足(&)运算和为1。现画图如下:

 

                                                        

 

  我们将一列中只有一个0的位叫特殊位。当我们删除画红圈的哪一行时,由于特殊位中只存在一个0,该特殊为(&)和势必为1。为了构造删除任意一个数都不能使它的(&)和为1,k个数要满足的条件为每个数都至少要有一个特殊         位。那么我们该如何解决这个问题呢?我们可以将其抽象为把j个特殊位分给i个数,这个问题的思考方式与第二类斯特林数的思考方式相类似,唯一不同是这个问题中,i个数都是有区别的。

  现在我们来建立递推公式dp[ i ][ j ]的意思为把j个特殊位分给i个数,dp[ i ][ j ]可以由两种情况递推而来。第一种,j-1个特殊位分给了i个数,i个数中每个数都至少存在了一个特殊位,那么第j个特殊位可以分给i个数中的任意一             个,有i种情况,结果为i*dp[ i ][ j-1 ];第二种,j-1个特殊位分给了i-1个数,则第j个特殊位必须单独分给第i个数,而第i个数又可以是i个数中任意一个,于是也有i种情况。

  综上所述,我们可以写出递推公式dp[i][j]=i * ( dp[i][j-1] + dp[i-1][j-1])。

  我们假设有t个特殊位要分给k个数(k <= t <= m-1),也就是dp[ k ][ t ],后面(m-1-t)位要去掉全是1和只存在一个0的情况,有(2k-k-1)m-1-t种情况。

  最后我们得到答案Ckn 2(n-k)(m-1)  ∑m-1t=k Ctm-1dp[ k ][ t ] * (2k-k-1)m-1-t )。

代码编写:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

ll combination[5005][5005],dp[5005][5005];

ll fast_POW(ll a ,ll b,ll c)
{
    ll ans=1;
    while(b)
    {
        if(b&1){
            ans=(ans*a)%c;
        }
        a=(a*a)%c;
        b>>=1;
    }
    return ans;
}

inline ll comb(ll n, ll m)
{
    return combination[n][m];
 } 
void solve(ll n ,ll m , ll q)
{
    for(int i=0;i<=5001;i++)
    {
        combination[i][0]=1;
        combination[i][i]=1;
    }
    for(int i=1;i<=5001;i++)
    {
        for(int j=1;j<=i;j++)
        {
            combination[i][j]=(combination[i-1][j] % q +combination[i-1][j-1] %q)%q;
        }
    } 
    for(int j=1;j<=5001;j++)
    {
        dp[1][j]=1;
    }
    for(int i=2;i<=5001;i++)
    {
        for(int j=i;j<=5001;j++)
        {
            dp[i][j]=i * ( (dp[i][j-1] + dp[i-1][j-1]) %q )%q; 
        }
    }
    
    
    
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ll ji=fast_POW(2, i ,q)-1;
        ans = (ans+comb(n , i)  * fast_POW(ji,m-1,q) %q * fast_POW(fast_POW(2,n-i,q),m-1,q)%q)%q;
    }
    ll ans1=n * fast_POW(2,(n-1)*(m-1),q) %q;
    for(int i=2;i<=n;i++)
    {
        ll od=1,oddi=comb(n,i) * fast_POW(2,(n-i)*(m-1),q) % q;
        ll os=fast_POW(2,i,q)-i-1;
        for(int j=m-1;j>=i;j--)
        {
            ans1+=oddi*comb(m-1,j)%q * dp[i][j] %q * od %q;//这里我是曾经用fast_POW(fast_POW(...)....)这样的形式来写,但不知为何判断题目时运行超时了。我推测是因为算法时间复杂度最大只能到O(n2),再大就无法接受了。
            ans1=ans1%q;
            od=od*os%q;
        } 
    }
    
    
    
    cout << (ans-ans1+q)%q;
}


signed main()
{

    ll n,m,q;
    cin >> n >> m >> q;
    solve(n,m,q);
    return 0;
}

 

posted on 2024-07-19 16:14  kiyotaka-ayanokoji  阅读(40)  评论(0编辑  收藏  举报