一道不算难的题居然可以挖出这么多东西……
# 题面
点击展开/折叠题面
一共有 n 种面额不同的货币,第 i 种面额为 $a_i$;这 n 种货币构成一个货币系统。如果这 n 种货币(每种货币使用任意多次)恰好能够支付金额 b,则称“该货币系统可以表示 b”。
现给出该货币系统,求出一个包含货币种类数最少的货币系统,使得任意金额要么两个系统都不能表示,要么两个系统都可以表示。输出最少的货币种类数。
# 解析
- 线性空间(用来证明一些性质)
对于货币系统 $\{a_i\}$,定义它的“表示集合”:
其实就是一个线性空间,数集 $F$ 是非负整数集、$V=\{a_i\}$ 是定义在 $F$ 上的线性空间,那么 $B$ 就是 $V$ 的张成 $\operatorname{span}(V)$。
要求出另一个 $V’$ 使得 $\operatorname{span}(V’)=\operatorname{span}(V)$,则根据线性空间的性质,$V’$ 是 $V$ 的极大线性无关组——基。
也就是说 $V’\subset V$ 且对于任意 $a\in V’$ 不能被 $V-\{a\}$ 线性表出。
- 完全背包
有了线性空间的性质,背包起来就容易多了——其实只需要删去 $a\in V$ 且可以被 $V-\{a\}$ 线性表出的 a 即可。
由于数集是非负整数,a 一定是被比它小的数线性表出的。则我们从小到大枚举 $a_i$,顺便维护当前能够线性表出的集合——就是一个完全背包,每次用 $a_i$ 去更新背包状态,这里可以用 bitset 优化,但是没啥必要……如果枚举到的 $a_i$ 已经可以被线性表出,那么就删掉它。
设 $m=\max\{a_i\}$,则复杂度为 $O(nm)$,比较简单、也是网上常见的做法,就不提供代码了。
- 生成函数
完全背包本质上就是个生成函数——我们可以定义如下生成函数:
那么 $H(x)$ 第 $i$ 次项系数就是 $i$ 能够被多少种方案线性表出。根据线性空间的结论,$a_i\in V’$ 不能被 $V-\{a_i\}$ 线性表出,那么如果 $a_i\in V’$,则 $a_i$ 只能被 $V$ 用一种方法线性表出——即它本身。对应到 $H(x)$ 则是第 $a_i$ 次项系数为 1。
但是直接上 NTT 是 $O(nm\log m)$,并跑不过(本来常数就大)。
然后就有一个比较常规但是挺巧妙的操作:
只写到这一步还不够——根据泰勒展开(也可以用微积分硬推,但是tly告诉我这就是泰勒展开):
于是
然后可以调和级数 $O(m\log m)$ 处理出 exp 后面的式子,然后再 $O(m\log m)$ 做个 exp。但是……多项式常数还是大啊,LOJ 卡 80pt。
- 同余最短路
第一次见到 :)
同余最短路的一个用法就是求哪些数可以被 $\{a_i\}$ 表示(可以用无限多次)。
首先非常显然的,如果 $x$ 可以被 $V$ 表示,那么 $x+a_i$ 也可以被表示。定义 $a_i$ 的剩余系 $[a_i]=\{0,1,\dots,a_i-1\}$,则设 $d(x)$ 表示 $V$ 的张成中最小的模 $a_i$ 余 x 的数,即 $d(x)=\min\limits_{b\bmod a_i=x}b(b\in\operatorname{span}(V))$,则有
并且我们发现,$\operatorname{span}(V)=\operatorname{span}(V’)$ 当且仅当 V 和 V’ 在 $a_i$($a_i\in V\cap V’$)的剩余系下 $d(x)$ 完全相同。证明非常简单,因为 $d(x)\bmod a_i=x$,如果存在 $d(x)$ 在 V 和 V’ 中不同,例如 V 中 $d(x)$ 比 V’ 中小,则 V 的 $d(x)$ 一定不能在 V’ 中被表示。换句话说,$d(x)$ 是 V 中能够被表示出的模 $a_i$ 余 x 的最小的数。
如何求 $d(x)$?可以想到最短路——点集 $V$、有向边集 $E$:
比较直观,以 $v_i$ 为源点跑一个最短路,$v_i$ 与 $v_{x}$ 的距离就是 $d(x)$。
根据线性空间,现在要从 V 中删除一些数得到 V’ 使得 $\operatorname{span}(V)=\operatorname{span}(V’)$。我们知道 $a_1$(假设 ${a_i}$ 是升序)一定同时在 $V$ 和 $V’$ 中,所以选定 $a_1$ 作为剩余系模数,建图。相当于在原图上删掉一些边,使得最短路不变,求剩下的边中不同长度的边的数量最小是多少。
那么在做最短路的时候,我们储存一下点 $v_x$ 的最短距离是从哪种边权的边转移过来的,记作 tra[x]
。如果有多种转移方式,则取边权最小的作为 tra[x]
,这样可以使剩下的边权的数量最小。特别的,tra[0]=-1
(初始值,没有任何转移的边)。
最后求 tra[]
数组有多少种不同的值即可。
# 源代码
- 生成函数 80pt
1 | /*Lucky_Glass*/ |
- 同余最短路
1 | /*Lucky_Glass*/ |
THE END
Thanks for reading!
> Linked 离忧-网易云