题目链接: Moamen and XOR
大致题意
给定n和k. 让你构造出一个长度为 n n n的序列a[], 每个元素小于 2 k 2^k 2k.
要求序列满足 a 1 a_1 a1 & a 2 a_2 a2 & a 3 a_3 a3 & … & a n a_n an ≥ a 1 a_1 a1 ⊕ a 2 a_2 a2 ⊕ a 3 a_3 a3 ⊕ … ⊕ a n a_n an
问: 一共有个满足要求的序列.
解题思路
思维
下文提到的数字请看做二进制的形式!!! 为了简便叙述, 记 a n d and and为原式左侧的结果, x o r xor xor为右侧的结果.
我们首先默认全部所有位都是1, 那一定有and == xor
.
接下来我们考虑把数字变小. 由于高位的变化会直接影响到数字大小, 因此我们考虑最高位的情况:
最高位全都是1的情况:
如果n为奇数, 则表明
a
n
d
and
and和
x
o
r
xor
xor的最高位都是1, 是相等的情况, 我们需要继续分析次高位.
此时答案贡献 = 次高位的答案贡献.
如果n为偶数, 则表明
a
n
d
and
and的最高位是1, 而
x
o
r
xor
xor的最高位是0, 是and大于xor的情况. 此时其他位可以任意排列, 均满足and > xor
.
此时答案贡献 = 其余位置的全排列总方案数
假设当前是第 i i i位, 则后方有 j = i − 1 j = i - 1 j=i−1可以进行全排列, 每一个位置有01两种取值, 因此一个数字共有 2 j 2^j 2j种情况, 一共有n个数字, 因此总的方案数为: ( 2 j ) n (2^j)^n (2j)n
最高位不全是1的情况:
我们考虑把其中的部分1改变位0. 此时and的最高位一定是0, 因此必须让xor的最高位也是0 则此时最高位的1一定有偶数个.
如果n为奇数的话, 则我们可以挑选{1, 3, 5, …, n}个位置, 使得它们的最高位变成0.
如果n为偶数的话, 则我们可以挑选{2, 4, 6, …, n}个位置, 使得它们的最高位变成0.而选择这些位置的方式为:
奇数: s u m = C n 1 + C n 3 + . . . + C n n sum = C_n^1 + C_n^3 + ... + C_n^n sum=Cn1+Cn3+...+Cnn
偶数: s u m = C n 2 + C n 4 + . . . + C n n sum = C_n^2 + C_n^4 + ... + C_n^n sum=Cn2+Cn4+...+Cnn
此时答案贡献 = sum * 次高位的方案贡献
到此为止, 我们已经分析清了所有的情况, 当分析其余位时, 认为高位不存在同理分析即可. 我们发现应当分类讨论. 即: 按照n的奇偶性分类.
设dp[], dp[i]表示第i位的贡献.
当n为奇数时: d p [ i ] = d p [ i − 1 ] ∗ ( 1 + s u m ) dp[i] = dp[i - 1] * (1 + sum) dp[i]=dp[i−1]∗(1+sum) 其中1为第i位全1时的方案, sum为第i位不全为1时的方案.
当n为偶数时: d p [ i ] = ( 2 i − 1 ) n + d p [ i − 1 ] ∗ s u m dp[i] = (2^{i-1})^{n} + dp[i - 1] * sum dp[i]=(2i−1)n+dp[i−1]∗sum 其中 ( 2 i − 1 ) n (2^{i-1})^{n} (2i−1)n为第i位全1时的方案, sum为第i位不全为1时的方案.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, mod = 1E9 + 7;
int fpow(int a, int b) { //快速幂
ll res = 1; a %= mod;
while (b) {
if (b & 1) res = res * a % mod;
b >>= 1;
a = 1ll * a * a % mod;
}
return res;
}
/* 组合数学部分 */
int num[N], innum[N];
void init(int n = N - 5)
{
num[0] = innum[0] = 1;
for (int i = 1; i <= n; ++i) {
num[i] = 1ll * num[i - 1] * i % mod;
innum[i] = 1ll * innum[i - 1] * fpow(i, mod - 2) % mod; //逆元
}
}
int C(int a, int b) { return 1ll * num[a] * innum[a - b] % mod * innum[b] % mod; }
/* 处理组合数前缀情况 */
int calcodd(int n) {
ll res = 0;
for (int i = 1; i <= n; i += 2) res = (res + C(n, i)) % mod;
return res;
}
int calceven(int n) {
ll res = 0;
for (int i = 2; i <= n; i += 2) res = (res + C(n, i)) % mod;
return res;
}
int dp[N];
int main()
{
init(); // 不会吧? 你写组合数学又忘了调用init()?
int t; cin >> t;
while (t--) {
int n, k; scanf("%d %d", &n, &k);
dp[0] = 1;
if (n & 1) {
int sum = calcodd(n);
for (int i = 1; i <= k; ++i) {
dp[i] = 1ll * dp[i - 1] * (1 + sum) % mod;
}
}
else {
int sum = calceven(n);
for (int i = 1; i <= k; ++i) {
dp[i] = 1ll * dp[i - 1] * sum % mod;
dp[i] = (dp[i] + fpow(fpow(2, i - 1), n)) % mod;
}
}
printf("%d\n", dp[k]);
}
return 0;
}