• 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。
      在这里插入图片描述

    题解

    逐步分析:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

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

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

  • 相关阅读:
    python-立方和不等式
    华为云云服务器评测 宝塔+nginx 同时部署Springboot、Vue项目
    【已解决】扫描mapper包
    JavaScript-Vue基础语法-创建-组件-路由
    疫情环境下外卖跑腿市场,校园平台与社会主流大平台有什么区别?
    ECMAScript modules规范示例详解
    11月业务安全月报 | 台湾2300万人信息泄露;黑客两分钟即可破解安卓锁屏;乌克兰“IT军团”入侵俄罗斯中央银行
    CentOS和Ubuntu中防火墙相关命令
    node教程(四)Mongodb+mongoose
    人生苦短,我用JRebel
  • 原文地址:https://blog.csdn.net/wjl_zyl_1314/article/details/126476067