2024牛客多校第一場A Bit Common & A Bit More Common

AcidBarium發表於2024-07-26

A Bit Common

時間限制:3s(C++/C) / 6s
記憶體限制:1048576K(C++/C) / 2097152K

題目描述

Given two integers \(n\) and \(m\), among all the sequences containing \(n\) non-negative integers less than \(2^m\), you need to count the number of such sequences \(A\) that there exists a non-empty subsequence of \(A\) in which the bitwise AND of the integers is \(1\).

Note that a non-empty subsequence of a sequence \(A\) is a non-empty sequence that can be obtained by deleting zero or more elements from \(A\) and arranging the remaining elements in their original order.

Since the answer may be very large, output it modulo a positive integer \(q\).

The bitwise AND of non-negative integers \(A\) and \(B\), \(A\ \texttt{AND}\ B\) is defined as follows:

  • When \(A\ \texttt{AND}\ B\) is written in base two, the digit in the \(2^d\)'s place (\(d \ge 0\)) is \(1\) if those of \(A\) and \(B\) are both \(1\), and \(0\) otherwise.

For example, we have \(4\ \texttt{AND}\ 6\) = \(4\) (in base two: \(100\ \texttt{AND}\ 110\) = \(100\)).

Generally, the bitwise AND of \(k\) non-negative integers \(p_1, p_2, \ldots, p_k\) is defined as \((\ldots((p_1\ \texttt{AND}\ p_2)\ \texttt{AND}\ p_3)\ \texttt{AND}\ \ldots\ \texttt{AND}\ p_k)\)
and we can prove that this value does not depend on the order of \(p_1, p_2, \ldots, p_k\).

輸入描述

The only line contains three integers \(n\) (\(1 \le n \le 5\,000\)), \(m\) (\(1 \le m \le 5\,000\)) and \(q\) (\(1 \le q \le 10^9\)).

輸出描述

Output a line containing an integer, denoting the answer.

示例1

輸入

2 3 998244353

輸出

17

示例2

輸入

5000 5000 998244353

輸出

2274146

筆者翻譯

給你兩個整數\(n\)\(m\),找一個長度為\(n\)的序列,序列中的每個元素(正整數)都小於\(2^m\),並且這個序列中存在一段子序列使得子序列的所有元素的按位與為\(1\),請問這樣的序列有多少種,由於答案特別大需要對\(q\)取模布拉布拉......

思路

關鍵詞按位與小於\(2^m\)
不難想到這道題中出現的數字不能簡單的當成十進位制的數字來看,當成二進位制數來看比較合適。也就是說,每一個元素都當作一個長度為\(m\)的矩陣來看會比較合適。

存在一段子序列使得子序列所有的元素的按位與為\(1\),假設這段子序列的長度為\(k\),這k個數在二進位制表示中的最後一位必須都是\(1\)(有的題解中會說是奇數),這樣才能保證最後一位按位與之後會得到\(1\)而不是\(0\)(如果有任何一個元素的最後一個元素存在\(0\),那麼最後按位與得到的數一定是\(0\))。我們不妨把這k個元素合在一起看成一個k行m列的矩陣,基於上述理論,這個矩陣的第m列也就是最後一列中的所有元素都為1;而對於前m-1列來說,每一列的按位與都必須為0(和最後一列的情況恰恰相反),也就是說每一列中至少有一個元素為0。每一列所有可能的情況有\(2^k\)種,除去全為1的情況,前\(m-1\)每一列都有\((2^k-1)\)種情況,\(m-1\)列就有\((2^k-1)^{m-1}\)

下面的這張圖或許能幫助你更好的理解

對於沒有被選種的\(n-k\)個元素來說,每個元素的最後一位必須是\(0\),前\(m-1\)位可以隨便,故有$2^{(n-k)\times(m-1)} $種情況。把上面的答案綜合在一起,同時由於從n個元素中選擇k個元素的時候需要進行一次排列組合,就是說乘以 \(C_n^{k}\).

那麼顯而易見的,最後的答案就是:\(\sum_{k=1}^{n} \left( C_n^k (2^k - 1)^{m-1} 2^{(n-k) (m-1)} \right)\)

具體的程式碼實現細節

其實式子列到這個地方,這道題的關鍵部分就已經結束了,剩下的都是些寫程式碼的無關緊要的小事罷。不過我還是說一些我覺得可以最佳化的地方。
首先是求組合數的時候,如果每個迴圈都單獨求一次組合數,未免太浪費時間了,比較常用的做法是在主程式開始之前就用\(O(n^2)\)把所有需要的組合數算完,需要的時候\(O(1)\)查詢,這樣會快的很多。計算組合數的時候我們需要使用公式\(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\).用程式碼具體實現的話就是

 rep(i, 0, 5004) _rep(j, 0, i) if (!j) c[i][j] = 1;
    else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
    //把C[i][0]標記為1,其他的用公式遞推
    //rep是for迴圈但是i<,_rep是i<=,下同

式子中還有很多求冪的地方,使用快速冪來最佳化

ll binpow(ll a, ll b, ll m)
{
    a %= m;
    ll res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

同時,式子中出現了許多求2的次方的地方,雖然使用快速冪可以在\(O(log)\)內完成計算,但如果我們事先把2的所有次方用\(O(nm)\)跑完,最後只需要\(O(1)\)來進行查詢,用程式碼實現就是

twoFang[0] = 1;
_rep(i, 1, 25000006)
    twoFang[i] = twoFang[i - 1] * 2 % mod;

最後只需要代入一點點算就行了

 _rep(i, 1, n)
    {
        ll cnk = c[n][i];
        ll temp = cnk * binpow((twoFang[i] - 1), m - 1, mod) % mod;
        temp = temp * twoFang[(n - i) * (m - 1)] % mod;
        ans = (ans + temp) % mod;
    }

第一問完整程式碼

點選檢視程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define endl '\n'
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define rep(i, a, b) for (int i = (a); i < (b); ++i)
#define Forget_that return
#define Just_go 0
#define Endl endl
#define ednl endl
#define eldn endl
#define dnel endl
#define enndl endl
#define Ednl endl
#define Edml endl
#define llmax 9223372036854775807
#define intmax 2147483647

int n;
ll mod;
ll twoFang[30000007];
ll c[5005][5005];

ll binpow(ll a, ll b, ll m)
{
    a %= m;
    ll res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    ll n, m;
    cin >> n >> m;
    cin >> mod;

    rep(i, 0, 5004) _rep(j, 0, i) if (!j) c[i][j] = 1;
    else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

    twoFang[0] = 1;
    _rep(i, 1, 30000006)
        twoFang[i] = twoFang[i - 1] * 2 % mod;

    ll ans = 0;

    _rep(i, 1, n)
    {
        ll cnk = c[n][i];
        ll temp = cnk * binpow((twoFang[i] - 1), m - 1, mod) % mod;
        temp = temp * twoFang[(n - i) * (m - 1)] % mod;
        ans = (ans + temp) % mod;
    }

    cout << ans;

    Forget_that Just_go;
}

沒有時間為第一問的解決感到開心,接下來到場的是第二問Orz

A Bit More Common

時間限制:3s(C++/C) / 6s
記憶體限制:1048576K(C++/C) / 2097152K

題目描述

Given two integers \(n\) and \(m\), among all the sequences containing \(n\) non-negative integers less than \(2^m\), you need to count the number of such sequences \(A\) that there exists two different non-empty subsequences of \(A\) in each of which the bitwise AND of the integers is \(1\).

Note that a non-empty subsequence of a sequence \(A\) is a non-empty sequence that can be obtained by deleting zero or more elements from \(A\) and arranging the remaining elements in their original order. Two subsequences are different if they are composed of different locations in the original sequence****.

Since the answer may be very large, output it modulo a positive integer \(q\).

The bitwise AND of non-negative integers \(A\) and \(B\), \(A\ \texttt{AND}\ B\) is defined as follows:

  • When \(A\ \texttt{AND}\ B\) is written in base two, the digit in the \(2^d\)'s place (\(d \ge 0\)) is \(1\) if those of \(A\) and \(B\) are both \(1\), and \(0\) otherwise.

For example, we have \(4\ \texttt{AND}\ 6\) = \(4\) (in base two: \(100\ \texttt{AND}\ 110\) = \(100\)).

Generally, the bitwise AND of \(k\) non-negative integers \(p_1, p_2, \ldots, p_k\) is defined as
\((\ldots((p_1\ \texttt{AND}\ p_2)\ \texttt{AND}\ p_3)\ \texttt{AND}\ \ldots\ \texttt{AND}\ p_k)\)
and we can prove that this value does not depend on the order of \(p_1, p_2, \ldots, p_k\).

示例1

輸入

2 3 998244353

輸出

7

示例2

輸入

5000 5000 998244353

輸出

530227736

筆者翻譯

這一題和第一問不同的地方在於,第一問只需要找到序列中含有子序列滿足題意的數列,第二問要求找到序列中存在兩個子序列,使得子序列的所有元素的按位與為\(1\),求這樣的序列有多少種。一言以蔽之,第一問對於特殊的子序列的數量沒有要求,第二問要求至少存在兩個子序列滿足題目的要求。

思路

都把第一問寫完了,這第一問肯定不是白寫的啊,我們不難發現,如果記序列中只含有一個子序列使得子序列的所有元素的按位與為1的序列數量為\(ans2\)的話,那麼上一問的答案減去\(ans2\)就是這道題的答案。
所以,這道題的重點就成了求序列中只含有一個子序列使得子序列的所有元素的按位與為1的序列數量。

一樣的,我們設該序列的這段子序列的長度為\(k\),這段子序列的所有元素的最後一位二進位制必須為\(1\)。對於前面的\(k\)\(m-1\)列,我們必須滿足這樣一條性質:每一行都有\(0\),且每一行都存在一個\(0\),這個\(0\)是這一列中的唯一一個\(0\);並且每一列中都有\(0\). 如此,假如刪去任意一個元素(刪除任意一行),將其他的元素按位與,都會至少在某一列中出現一個\(1\)(因為每一個都存在一個\(0\)使得這個\(0\)是這一列中唯一的一個\(0\)).那麼這段子序列就是符合要求的,因為這個子序列不存在一個子序列使得其元素按位與為1.也就是說,這個子序列是這個序列中唯一一個滿足題意的子序列。

特別的,當這段子序列長度為\(1\)時,由於其不存在子序列,所以需要分開來單獨討論。我們不難發現,如果,這個子序列長度為\(1\),為了滿足條件,這個子序列中的唯一一個元素只能是\(1\).這個子序列從\(n\)個元素中選出,就是\(C_n^1 \times\ 1\)

剩下的,我們只需要找到滿足下面性質的矩陣數量就行了

  • \(k\)\(m-1\)
  • 矩陣中的元素不是\(1\)就是\(0\)
  • 每行都有\(0\)是這一列中唯一一個\(0\)
  • 每行可以有很多\(0\)
  • 每一列都必須有一個0

不難發現,這個矩陣按列進行劃分,可以劃分成兩種列

  1. 每一列含有兩個以上包括兩個的0
  2. 每一列只有一個0,同時所有的這些列的0會在矩陣的所有k行中出現(矩陣的每一行都有這些0中的某些)

我們設第二種情況的列有\(t\)列,相應的,第一種情況的列有\(m-1-t\)

首先對於第一種情況

對於每一列來說都有\(2^{k}\)種情況,除去一列裡面全是1的情況有\(2^{k}-1\)種情況,再除去每一列種含有1個0的情況就是我們需要的答案為\(2^{k}-1-k\).由於這樣的列共有\(m-1-t\)列,則共有\((2^{k}-1-k)^{m-1-t}\)種情況。

接著對於第二種情況

直接計算會有點麻煩,讀者不妨自己試一下,這裡我使用大家都推薦的一種方法---動態規劃
我們設滿足下麵條件的矩陣的數量為\(dp_{ij}\)

  • \(i\)\(j\)
  • 每一列都只有\(1\)\(0\)(這些\(0\)出現在所有的\(i\)行中)
  • 每一行都有\(0\)

先說結論:\(dp_{ij}=i\times\ (dp_{i-1j-1}+dp_{ij-1})\)

解釋

總體思想就是:第\(j\)列的狀態可以從第\(j-1\)列轉化過來。
對於\(i\)\(j-1\)列的滿足條件的矩陣,可以在最右側加入第\(j\)列,在第\(j\)\(i\)行中的任意一行中填一個\(0\),其他的地方填上\(1\)就可以使得新的到的這個\(i\)\(j\)列的矩陣滿足要求(\(i\)行共\(i\)種)。下面的圖可能會更方便理解一些:

如圖所示,將一個$3\times3$的滿足題意的矩陣按照上述方式可以得到一個\(3\times4\$的滿足題意的矩陣,這個矩陣的前3列組成的矩陣就已經滿足題意。 同時,有一種前3列組成的矩陣並不滿足題意的情況,即從\)i-1\(行\)j-1\(列的滿足題意的矩陣轉移來。 對於一個\)i-1\(行\)j-1\(列的滿足題意的矩陣,我們首先在最右側擴充第\)j$列,並在任意行新增第一行,在這一行的最右列填上\(0\),其他地方填上\(1\)即可。(由於共有\(i\)行,所以新增任意一行的添法共有\(i\)種)。下面的這張圖可能會更方便您理解一點

如圖所示,將一個$2\times3$的滿足題意的矩陣變成了三個$3\times4$的滿足題意的矩陣,由於前3列所組成的矩陣並不滿足題意,所以顯然的,這種情況和上一種情況是相互獨立的。

故而\(dp_{ij}=i\times\ (dp_{i-1j-1}+dp_{ij-1})\)

綜上所述,共有\(\sum_{k=2}^{n} C_n^k \sum_{t=k}^{m-1} C_{m-1}^t (2^k - k - 1)^{m-1-t} dp_{kt} + C_n^1 2^{(n-1)(m-1)}\)
最後拿第一問的答案減去上面這個就行了。

具體的程式碼實現細節

首先是那個動態規劃的部分

  dp[1][1] = 1;
  dp[0][0] = 1;
  _rep(i, 1, 5003) _rep(j, i, 5003)
     dp[i][j] = i * (dp[i - 1][j - 1] + dp[i][j - 1]) % mod;

當子序列長度為1時的特殊情況

 ans = (((ans - n * twoFang[(n - 1) * (m - 1)] % mod) % mod) + mod) % mod;

對於非特殊情況的求解

    _rep(k, 2, n)
    {
        ll temp = c[n][k];
        ll suffix = 0;
        ll btm = 1;
        for (int t = m - 1; t >= k; t--)
        {
            ll mark = c[m - 1][t];
            mark = mark * btm % mod;
            mark = mark * dp[k][t] % mod;
            suffix = (suffix + mark) % mod;
            btm = btm * (twoFang[k] - k - 1) % mod;
        }
        temp = temp * suffix % mod * twoFang[(n - k) * (m - 1)] % mod;
        ans = ((ans - temp) % mod + mod) % mod;
    }

這裡有一點需要注意,筆者在第一次寫的時候,求\((2^k - k - 1)^{m-1-t}\)的時候用的是快速冪,但是給時間複雜度增加了一個\(log\),不出意外的超時了。解決辦法就是,從\(m-1\)開始算,算到\(k\);而不是從\(k\)算到\(m-1\),每次用\(btm\)儲存那個冪數,每次對\(btm\)乘$(2^k-k- 1) $ .如此時間複雜度少了一個\(log\),完美地解決了問題。

完整程式碼

點選檢視程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define endl '\n'
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define rep(i, a, b) for (int i = (a); i < (b); ++i)
#define Forget_that return
#define Just_go 0
#define Endl endl
#define ednl endl
#define eldn endl
#define dnel endl
#define enndl endl
#define Ednl endl
#define Edml endl
#define llmax 9223372036854775807
#define intmax 2147483647

int n;
ll mod;
ll twoFang[30000007];
ll c[5005][5005];
ll dp[5005][5005];

ll binpow(ll a, ll b, ll m)
{
    a %= m;
    ll res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    ll n, m;
    cin >> n >> m;
    cin >> mod;

    rep(i, 0, 5004) _rep(j, 0, i) if (!j) c[i][j] = 1;
    else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

    twoFang[0] = 1;
    _rep(i, 1, 25000006)
        twoFang[i] = twoFang[i - 1] * 2 % mod;

    ll ans = 0;

    _rep(i, 1, n)
    {
        ll cnk = c[n][i];
        ll temp = cnk * binpow((twoFang[i] - 1), m - 1, mod) % mod;
        temp = temp * twoFang[(n - i) * (m - 1)] % mod;
        ans = (ans + temp) % mod;
    }

    ans = (((ans - n * twoFang[(n - 1) * (m - 1)] % mod) % mod) + mod) % mod;

    dp[1][1] = 1;
    dp[0][0] = 1;
    _rep(i, 1, 5003) _rep(j, i, 5003)
        dp[i][j] = i * (dp[i - 1][j - 1] + dp[i][j - 1]) % mod;

    _rep(k, 2, n)
    {
        ll temp = c[n][k];
        ll suffix = 0;
        ll btm = 1;
        for (int t = m - 1; t >= k; t--)
        {
            ll mark = c[m - 1][t];
            mark = mark * btm % mod;
            mark = mark * dp[k][t] % mod;
            suffix = (suffix + mark) % mod;
            btm = btm * (twoFang[k] - k - 1) % mod;
        }
        temp = temp * suffix % mod * twoFang[(n - k) * (m - 1)] % mod;
        ans = ((ans - temp) % mod + mod) % mod;
    }

    cout << ans;

    Forget_that Just_go;
}

總結

雖然像我這麼做,時空複雜度都不小,第二問花了1700多毫秒才過。但是好歹過了。具體的最佳化可以根據n、m來自主分配所需要的空間和常量。

相關文章