A题
题意
用 0 − 2 m 0-2^m 0−2m的数字组成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(n−k))(m−1),即
2
(
n
−
k
)
(
m
−
1
)
{2^{(n-k)(m-1)}}
2(n−k)(m−1)种,而奇数每行都要至少有一个
0
0
0,所以要排除一行全为
1
1
1的情况,所以奇数个数有
(
2
k
−
1
)
(
m
−
1
)
{(2^{k}-1)}^{(m-1)}
(2k−1)(m−1)种,又因为
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(n−k)(m−1)⋅(2k−1)(m−1)⋅(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;
}
题意
和A题基本一样,只是需要找到两个子序列满足 A N D ( 与 ) AND(与) AND(与)为 1 1 1。
思路
从A题我们找到了至少有一个子序列满足的情况,那么两个子序列是不是只要再减去只有一个子序列的种数即可,我们还是分成偶数和奇数两部分,如果是偶数,和
A
A
A题一样也为
2
(
n
−
k
)
(
m
−
1
)
{2^{(n-k)(m-1)}}
2(n−k)(m−1),对于奇数,由于
k
=
1
k=1
k=1时全部都不满足,所以我们可以直接从
k
=
2
k=2
k=2开始枚举,而
k
≥
2
k \ge 2
k≥2时只有当
k
k
k个数中二进制的
m
−
1
m-1
m−1位中每一位都只有唯一的
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
j−1个特殊位推,可以是本来就有
k
k
k个数,每一个数都可以贡献出
1
1
1个特殊位,也可以是
k
−
1
k-1
k−1个数,可以是新加的数贡献
1
1
1个特殊位,也可以是原来的
k
−
1
k-1
k−1个数贡献,新加的一个数只是作为普通值,所以可以推出式子
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∗(fk−1,j−1+fk,j−1)
其他位除了不能全为
1
1
1,还要排除只有
1
1
1个0的情况,即为
(
2
k
−
k
−
1
)
m
−
1
−
j
{(2^k-k-1)}^{m - 1 - j}
(2k−k−1)m−1−j,j为特殊位的个数,还要计算从
m
−
1
m-1
m−1位中挑出
j
j
j个特殊位,为
(
m
−
1
j
)
{m-1 \choose j}
(jm−1)。
所以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=km−1((jm−1)⋅dpk,j⋅(2k−k−1)m−1−j)
所以每次答案为
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(n−k)(m−1)⋅(2k−1)(m−1)⋅(kn)−∑j=km−1((jm−1)⋅dpk,j⋅(2k−k−1)m−1−j)
对
k
k
k从
2
−
n
2-n
2−n累加即可。
#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;
}