Codeforces1557C Moamen and XOR (组合数学 思维)

99 篇文章 2 订阅
12 篇文章 0 订阅
博客主要探讨了一道编程题目,涉及动态规划和位运算。题目要求构造一个长度为n的序列,所有元素小于2^k,并且序列的按位与大于或等于按位异或。解题策略分为高位全是1和不全是1两种情况,通过计算不同位的贡献来得出答案。代码使用C++实现,包含快速幂和组合数学计算。
摘要由CSDN通过智能技术生成

题目链接: 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=i1​可以进行全排列, 每一个位置有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[i1](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]=(2i1)n+dp[i1]sum 其中 ( 2 i − 1 ) n (2^{i-1})^{n} (2i1)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;
}

END

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值