zoukankan      html  css  js  c++  java
  • 浅析容斥和DP综合运用

    浅析容斥和DP综合运用

    前言

    众所周知在数数题中有一种很重要的计数方法——容斥。但是容斥有一个很大的缺陷:枚举子集的复杂度过高。所以对于数据规模较大的情况会很乏力,那么我们就只能引入容斥DP。

    复习一下容斥

    什么情况下用容斥?容斥能干什么?

    容斥的基本功能就是当你知道任意个指定集合的交集,你就能推出这些集合的并集。

    形式化的来说,就是:

    [left|igcup_{i=1}^{n} A_{i} ight|=sum_{i=1}^{n}left|A_{i} ight|-sum_{1 leq i<j leq n}left|A_{i} cap A_{j} ight|+sum_{1 leq i<j<k leq n}left|A_{i} cap A_{j} cap A_{k} ight|-cdots+(-1)^{n-1}left|A_{1} cap cdots cap A_{n} ight| ]

    只使用容斥朴素算法

    如果我们只会容斥,我们该怎么做?很显然根据上面的公式,我们需要枚举任意集合的组合方式,然后统计他们的答案,将他们加入答案。

    比如说在【线上训练 5】乘方中,当我们枚举出子集,我们就很容易求出子集的大小。

    【线上训练3】数个数,当我们枚举出了子集,我们也能统计出子集的大小

    我们通过以上两道题,总结出了这种容斥题的一个特点:都是求集合的并集,同时你可以通过一些方式求得集合的交集。

    使用DP进行优化

    我们思考一下就会发现,上面两道题的复杂度瓶颈都在于需要(2^k)的枚举出所有的子集再进行DP。那我们就可以考虑进行DP。因为对于一个子集,添加一个元素,就会导致他贡献的符号取反。

    一般DP状态都是(dp[i][j]),其中(i)代表前(i)个集合中的元素。而(j)一般代表一个决定交集大小的值。而对于(j)值相同的所有状态(子集),在它们之后再添加一个元素,对答案增加的贡献都一样。

    举个例子:

    【线上训练3】数个数中,如果往一个子集内加入新的元素,子集的大小就会增加((字符集)^{(加入的区间位置-上一个区间位置)})。所以我们记录的(j)就是上一个区间的位置。
    而在【线上训练 5】乘方中,如果往一个子集内加入新的元素,子集的大小就会变成(lcm(j,N_i))。所以(j)记录的就是选择的子集的(lcm)

    对于前一道题而言,因为决定所选子集的大小是子集中元素的间隔距离。所以我们需要一边(dp)选择元素,一边把每一次往子集里添加元素增加的贡献累加进入最终答案。

    而对于后一道题而言,因为决定所选子集的大小是子集中元素的(lcm),因为这是一个数,而且这个数和前面说的转移答案所需要的(j)是同一个数,所以我们可以只在(dp)数组里记录容斥系数的和,等到最后再来统计答案。

  • 相关阅读:
    【转】sql server编写通用脚本自动检查两个不同服务器的新旧数据库的表结构差异
    Pytest 2
    【转】python通过SMTP协议发送邮件失败,报错505或535
    【转】环境搭建之allure的安装配置,及简单使用
    Pytest 1
    替换姓名为隐式
    docker 用户组权限
    安装go环境
    Win10配置WSL2安装Ubuntu,并支持Nvidia CUDA 环境
    miniconda源配置
  • 原文地址:https://www.cnblogs.com/GavinZheng/p/11726437.html
Copyright © 2011-2022 走看看