计数杂题
部分题目来自这个题单。
Luogu P6075 [JSOI 2015] 子集选取
首先,发现可以把元素单独拎出来考虑,假设对于一个元素来说有
继续转化题意。
对于
也就是说,如果
然后想象一种合法方案,容易发现每种方案就相当于把整个三角形分成两半,左上一半右下一半。
每两种方案不同当且仅当左上右下的分界线不同。
于是我们就能很轻松地求方案数了,即一个分界线从左下走到右上的方案。每一步可以向左或者向右走,即
快速幂即可。
Luogu P6146 [USACO 20 FEB] Help Yourself G
首先为了划分未求解和已求解的部分,我们需要把线段按照左端点排序。
给一个不排序会遇到错误的例子:
1 | ---- 1 ------3 复制 |
用下面的做法做的话,先算
然后设
考虑如何推出
- 前
个线段的答案 - 前面所有子集加上第
条线段的答案。
发现后者实际上也是分两部分的:
- 新增的连通块(复杂度)
- 除去新增,也就是原有的复杂度
好了,那么新增的连通块怎么算?实际上就是与第
那我们可以把前
然后问题就转化为:对前
容易发现就是与线段
得到递推式:
用快速幂优化,复杂度
P6008 [USACO 20 JAN] Cave Paintings P
首先发现答案就是把每个连通块的方案数乘起来。
那对于一个连通块怎么算答案?一个比较蛋疼的事实是,即使是同一个连通块,我们也不能保证它每个部分的水位是一样的,比如下面这个例子:
1 | ######## 复制 |
显然左右侧在分割开的时候可以水位高度不同。
那我们能不能对分割开的两块分别算呢?答案是不行,因为连通器原理。
1 | ######## 复制 |
初中物理可知,左右两侧水位必须一样高。
所以我们需要动态维护这个关系,用啥呢?并查集。
初始化每个格子答案为 1,然后对于每个连通块从下往上涨水,过程如下:
- 把这一行相邻的能放水的位置连在一起,表示他们必须是一个水位的。
- 遍历该行每一个空位,如果这个位置下面也是空位且二者目前没有相连,那么当前位置方案数
*=
正下方一格方案数,同时把两个格子相连,表示两个连通块接在一起了。 - 遍历该行每一个空位,每遇到一个连通块(
find(x) == x
)就把该格方案数加一。
最后用前面提到过的判断连通块的方式把答案乘起来就好了。
Luogu P1350 车的放置
同样是蓝题,但是比上一道水多了。
每一行每一列都只能放一个棋子,那直接从上往下遍历行,同时枚举放几颗棋子递推计数即可。
具体来说,设
其中
记得把
Luogu P3223 [HNOI 2012] 排队
基本上是纯数学题。
大概计数题可以分两种大方向思考:正推、容斥。
而容斥也大概分两种:手动容斥和套式子反演。
想直接拆分算,感觉是一个非常恶心难想的分类讨论。
递推,设
那就手动容斥。
容易发现把老师的限制扔掉之后是可以轻松算出来答案的:
然后不合法方案,即两个老师靠在一起的方案,把两个老师绑在一起当一个男生求出来所有合法方案即可。别忘了两个老师顺序可以变,所以加一个
最终答案:
为啥说基本上是纯数学题而不是完全的数学题?因为这题需要打高精。
然后我用 python 写的,python
整数除法返回浮点值,所以要强制整除,即使用 //
。
举个例子,算
1 | import math 复制 |
[CSP-S 2019] Emiya 家今天的饭
挺难的题。
首先我们可以把选菜看成从方阵里面选数,每行只能选一个,每列最多选
如果我们分列来考虑,发现合法的列有很多,但是不合法的列只会出现一个。(显然选的数超过总数一半的列只会有一个吧)
换句话说,如果我们正着算,记录合法方案总数,是一件非常困难的事情,因为每一列的合法情况我们都要记录。但是相对的,对于不合法方案计数,我们可以枚举不合法的列进行递推。当我们保证这一列不合法时,其他列一定合法,这样我们就不用记太多状态了。
枚举到第
然后不合法方案数即
复杂度
根据刚才不合法方案数的计算,我们发现,一个方案不合法,当且仅当有一列选的数大于其他列选的数之和。
换句话说,刚才对不合法方案数的统计,基于
设
不合法方案数:
容易发现这个东西可能是负数,所以实现的时候要给
然后是计算总方案数,设
最终答案:
Luogu P3214 [HNOI 2011] 卡农
题意要求选取子集有三条限制:
- 非空 - 选出的子集两两不同 - 所有元素的出现次数为偶数
转化 1:将“子集”转化为“二进制数”,然后选取子集变成从
- 这些数不等于
转化 2:答案是“无序”的方案数,我们发现这个
然后我们发现这上面几条限制挺难同时记录并递推的,那我们可以考虑一下排除不合法方案。
设
然后考虑第一条限制,发现第
最后考虑第三条性质:假设第
然后就可以递推求解了,
CF 932 E
模拟赛写不动题了过来写这玩意挺合理的吧
原题意是给定
然后我们发现这个东西只与
把
继续组合意义分析,上面那个式子相当于是:从
继续发现发现这
(确定
第二类斯特林数可以
CF 1850 G
感觉这题没啥必要放,毕竟 CF 1500,但是我写的
发现满足要求的两个点只有四种可能: - 横坐标相等 - 纵坐标相等 - 横纵坐标和相等 - 横纵坐标差相等
所以我们把所有点按照上面四个元素作为关键字排序四次,每次分别统计相等的个数
感觉比他们写的 std::map
做法常数要小。
CF 1485 F
简化题意
给定数组
合法的
我们一个一个数填,发现对于每一个
然后设
发现,第一个操作就是把整个数组向右移动了
第二个实际上等于一个位置前面所有元素的和,
值域很大,所以要用 std::map
或者
std::unordered_map
,后者需要重写哈希函数。
示例代码
1 | cin >> n; 复制 |
CF 449 D
简化题意
给定数组
设
显然
但是发现这个
然后继续发现,
总体流程:先对
示例代码
1 | i32 f[N]; 复制 |