P6075 [JSOI2015]子集选取

本文针对P6075题提供一种解题思路。看到题面首先想到DP但因和的范围放弃,转而用深搜。经分析发现答案是2kn,通过对集合元素拆解处理证明了该结论。还利用费马小定理对指数取模进行优化,虽为快速幂板子题但可减少运算量。
链接:P6075

前言:

虽然其他大佬们的走分界线的方法比我巧妙多了,但还是提供一种思路。


题意:

%&¥……@#直接看题面理解罢。


分析过程:

看到这样的题面我脑里第一反应就是DP,但是看到nk的范围只能作罢。想到各种柿子又根本推不出来,于是颓废地打了个复杂度算不来的貌似是 2 n 3 2^{n^3} 2n3 的深搜。于是有以下测试:

input      output

1 2         4

2 2         16

3 2         64

1 3         8

2 3         64

3 3        512

于是我们惊喜地发现答案貌似就是 2 k n 2^{kn} 2kn。但这个答案到底是怎么来的呢?


证明:

我们发现对这道题,所谓集合是可以拆解成n个元素分别处理的,可将其视为从三角形左上角起向右下进行连续的覆盖,如图:

那么设一个元素在大小为k的三角形内的覆盖方案数为 f ( k ) f(k) f(k) ,那么n个元素的方案总数即为 f ( k ) n f(k)^n f(k)n 。接下来来推 f ( k ) f(k) f(k) ,注意以下推理仍只关注一个元素。

对于一个大小为k的三角形,我们着重分析最下面一行,因为去掉这一行就能转化为更小的三角形,将覆盖,未覆盖以及任意取值分别看做“1”,“0”,和“?”,那么根据题意,这一行的状况只能是前面m个1,后面k-m个0,分情况讨论。

  • 如果这行全部为零,即 :

发现当前的方案数即为上面未确定三角形的方案数 f ( k − 1 ) f(k-1) f(k1)

  • 如果前面有 m ( 1 ≤ m < k − 1 ) (1\le m< k-1 ) (1m<k1) 个1,即:

发现当前的方案数即为右上角缺失的三角形的方案数 f ( k − 1 − m ) f(k-1-m) f(k1m)

  • 如果前面有k-1个1,即:

那么最后一位可填0或1,共2种方案。

总结一下,发现第一种和第二种可合并为 ∑ i = 1 k − 1 f ( i ) \sum\limits_{i=1}^{k-1}f(i) i=1k1f(i),为了美观,我们设 f ( 0 ) f(0) f(0) 为2,即可将第三种情况也合并,即:

f ( k ) = ∑ i = 0 k − 1 f ( i ) , f ( 0 ) = 2 f(k)=\sum\limits_{i=0}^{k-1}f(i),f(0)=2 f(k)=i=0k1f(i),f(0)=2

  • k = 1 k=1 k=1

f ( 1 ) = f ( 0 ) = 2 = 2 1 f(1)=f(0)=2=2^1 f(1)=f(0)=2=21

  • k > 1 k>1 k>1

因为 f ( k − 1 ) = ∑ i = 0 k − 2 f ( i ) f(k-1)=\sum\limits_{i=0}^{k-2}f(i) f(k1)=i=0k2f(i)

所以 f ( k ) = ∑ i = 0 k − 1 f ( i ) = ∑ i = 0 k − 2 f ( i ) + f ( k − 1 ) = 2 ∗ f ( k − 1 ) f(k)=\sum\limits_{i=0}^{k-1}f(i)=\sum\limits_{i=0}^{k-2}f(i)+f(k-1)=2*f(k-1) f(k)=i=0k1f(i)=i=0k2f(i)+f(k1)=2f(k1)

综上, f ( k ) = 2 k f(k)=2^k f(k)=2k

那么那么n个元素的方案总数即为 f ( k ) n f(k)^n f(k)n 2 k n 2^{kn} 2kn


优化:

呐有人就要问了这不就是个快速幂板子题吗,有什么优化?对不起的确是有的。

由于我们取模的数1,000,000,007是个质数,所以有费马小定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp),也就是说我们可以对指数取模从而减少那么几次运算量,即 2 k n m o d    1000000006 2^{kn\mod 1000000006} 2knmod1000000006


代码:

不就是个快速幂板子吗,就不放代码了。


题外话:

很睿智的作者看到 nk 的范围大,于是反手就把k*n对1,000,000,007取了个模。(100->40)

有人就要问了,这道绿题你写这么长给谁看啊?没错这篇题解就是我用来练 LaTeX \LaTeX LATEX的!

的范围大,于是反手就把k*n对1,000,000,007取了个模。(100->40)~~

有人就要问了,这道绿题你写这么长给谁看啊?没错这篇题解就是我用来练 LaTeX \LaTeX LATEX的!

引用中未提及P2143 [JSOI2010]巨额奖金的相关信息,但有BZOJ 1016 JSOI 2008巨额奖金的问题描述。该问题是在一个有n个区、m条干道的城市规划交通枢纽,要将部分干道改进为新型干道,使任何两个区可通过新型干道直接或间接连接,已知每条干道改进费用,求建设新型干道总费用最小的方案数,输出方案总数除以31011的模。 解题思路如下: 1. 先使用Kruskal算法求出最小生成树的权值,同时记录每种权值的边在最小生成树中使用的数量。 2. 对于每种权值的边,通过枚举其所有可能的组合情况,判断这些组合能否构成满足条件的部分生成树,统计满足条件的组合数。 3. 根据乘法原理,将每种权值边的组合数相乘,得到最终的方案数,再对31011取模。 以下是代码实现示例(Python 伪代码): ```python MOD = 31011 # 边的类 class Edge: def __init__(self, u, v, w): self.u = u self.v = v self.w = w # 并查集查找操作 def find(parent, x): if parent[x] != x: parent[x] = find(parent, parent[x]) return parent[x] # 并查集合并操作 def union(parent, rank, x, y): root_x = find(parent, x) root_y = find(parent, y) if rank[root_x] < rank[root_y]: parent[root_x] = root_y elif rank[root_x] > rank[root_y]: parent[root_y] = root_x else: parent[root_y] = root_x rank[root_x] += 1 # Kruskal算法求最小生成树 def kruskal(edges, n): edges.sort(key=lambda x: x.w) parent = [i for i in range(n + 1)] rank = [0] * (n + 1) mst_edges = [] total_weight = 0 for edge in edges: u = edge.u v = edge.v w = edge.w root_u = find(parent, u) root_v = find(parent, v) if root_u != root_v: union(parent, rank, u, v) mst_edges.append(edge) total_weight += w return mst_edges, total_weight # 统计每种权值边的方案数 def count_schemes(edges, n): mst_edges, _ = kruskal(edges, n) weight_count = {} for edge in mst_edges: if edge.w not in weight_count: weight_count[edge.w] = 0 weight_count[edge.w] += 1 total_schemes = 1 for weight, count in weight_count.items(): # 这里需要具体实现枚举组合并判断的逻辑 # 由于代码复杂,此处省略具体实现 # 假设 valid_combinations 是该权值边的有效组合数 valid_combinations = 1 total_schemes = (total_schemes * valid_combinations) % MOD return total_schemes # 主函数 def main(): # 读取输入 n, m = map(int, input().split()) edges = [] for _ in range(m): u, v, w = map(int, input().split()) edges.append(Edge(u, v, w)) # 计算方案数 schemes = count_schemes(edges, n) print(schemes) if __name__ == "__main__": main() ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值