AtCoder Regular Contest 146 C Even XOR题解

原题链接

https://atcoder.jp/contests/arc146/tasks/arc146_c

题意

输入一个参数N,你可以从[0 , 2N - 1] 中任意挑选元素来构成一个集合S,要求是集合S的子集T至少满足以下两个条件之一:

  1. 子集T中元素个数为奇数
  2. 子集T中所有元素的异或和不为0
    问最多可以构成多少个满足要求的集合S?结果取模998244353。
    AtCoder Regular Contest 146 C Even XOR题解_第1张图片

题解

逐步分析:

  1. 集合S至少满足两个条件之一等价于集合S中不可以有元素个数为偶数并且异或和为0的子集

  2. 假设集合S满足条件后,之后再加入什么元素会使得S’依然满足条件呢?
    或者说加入什么元素会使得S’不再符合要求呢?

    1. 集合S元素个数为偶数,那么加入任何元素都满足条件1
    2. 集合S元素个数为奇数,且异或和为X,那么当加入一个新的X元素时会使得异或和为0。

    要使得集合S(不论奇偶)依然满足条件,也必须使得子集也满足条件。所以对于集合S的每一个子集T的异或和X都不可以作为新元素加入。

    问题:那么所有子集T的异或和X不重复的个数有多少个呢?

    先假设T和U是集合S的奇数个数子集,并且T和U不相同。可以论证出两者的异或和也必然不同:
    论证:因为假设集合V是T和U的合集(可能出现同一个元素有偶数个情况,因为偶数个相同元素异或和为0且任何数异或0等于其本身,所以可以剔除掉偶数个的元素),那么V也一定是S的子集,并且元素个数一定为偶数。那么V的异或和等XORT ^ XORU= 0。也就是V不满足条件,这与S符合条件相悖。
    简单举个例子可以想一想就能理解上面叙述的:T = {a,b,c} , U = {a,d,e},那么 V = {b,c,d,e} (两个a异或和为0可以不用管)
    前面的问题也就迎刃而解了,所有奇数个数子集T的异或和X不重复的个数 == 所有奇数个数子集的个数。
    假设元素S有k个元素,那么奇数个数子集的个数=
    c(k,1) + c(k,3) + … + c(k,k) (k为奇数)
    c(k,1) + c(k,3) + … + c(k,k-1) (k为偶数)
    == 2k-1 (这个推导可以画个杨辉三角,c[i][j] = c[i-1][j-1] + c[i-1][j],可以看出奇数位和偶数位所加的上一层的每个元素的个数总和是一样的,所以结果等于 2k / 2 )

  3. 到这里其实动态规划的初型已经出来了,这里先把动态转移方程列出来:
    dp[i] 代表元素个数为i且满足条件的子集,显而易见dp[0] =1,最终结果为sum{dp[i]}
    在这里插入图片描述
    dp[i] = dp[i-1] * (可以添加元素总种类(2N)- 奇数个数子集个数(2(i-1)-1 = 2i-2)) / i
    i 代表的含义是 i个元素每一个元素都有可能是新加入的元素,也就是会重复计算c(i,1) = i 次,所以需要除以i。
    根据动态转移方程,可以看出当i >= N+2 时 已经没有可以再添加的新元素了,所以总的时间复杂度为O(N)。

PS:编写代码时,注意取余。

AC代码

#include 
using namespace std;
#define LL long long
#define io_init            \
  ios::sync_with_stdio(0); \
  cin.tie(0);              \
  cout.tie(0);
const int maxn = 2e5 + 5;
const LL mod = 998244353;
int n, k, m;
LL dp[maxn];

LL quickpow(LL a, LL b) {
  LL ret = 1;
  while (b != 0) {
    if (b & 1) {
      ret = ret * a % mod;
    }
    a = a * a % mod;
    b >>= 1;
  }
  return ret;
}

int main() {
  io_init;
  cin >> n;
  LL pow_2_n = quickpow(2, n);
  LL pow_2_i = 1;
  dp[0] = 1;
  dp[1] = pow_2_n;
  LL ans = dp[0] + dp[1];
  for (int i = 2; i < n + 2; ++i) {
    dp[i] = dp[i - 1] * (pow_2_n - pow_2_i + mod) % mod * quickpow(i, mod - 2) %
            mod;
    pow_2_i = pow_2_i * 2 % mod;
    ans = (ans + dp[i]) % mod;
  }
  cout << ans;
  return 0;
}

个人感觉好久没写这么详细的题解了,也很锻炼书面语言表达能力。

如有问题,欢迎指正与讨论!

你可能感兴趣的:(动态规划—dp,c++,atcoder,ARC146)