DP 专练
A - 跳蚤电话
观察性质,可以发现每次连边的点一定是有祖先关系的,可以直接挂上去一个,也可以是在中间边上插入一个点。
所以我很自然的想到去计算树上的点的加入顺序,因为一但加入顺序确定,每一次的操作也就随之确定。
这东西有点类似于拓扑序计数,只不过是有一些限制。
对于一颗子树来说,我们可以发现如果root排在了第一个位置,那么对于所有儿子对应的子树就可以随便排列,也就是一个可重集排列。
另一种情况如果root不在第一个位置,那么root前面的点一定来自于同一个儿子对应的子树,root后面又是一个可重集排列。
根据以上性质我们可以考虑DP,设 \(dp_{x,i}\)为考虑x子树,x节点第i个加入所对应的方案数,\(sum_x\) 表示当前x子树并上来了多少节点,\(all_x=\sum_{i=1}^{siz_x}dp_{x,i}\)。
\(dp_{x,i}=(dp'_{x,i} \times C_{sum-i+siz_v}^{siz_v}+(i!=1)\times dp'_{x,1}\times C_{sum-i+siz_v}^{siz_v-i+1})\times all_v\)
这样得到一个复杂度上界为 \(O(n^2)\) 的做法。
观察发现,dp的实质就是考虑根的位置还有根前面的点来自于谁,于是直接枚举就行,没有必要dp。一个点会被所有的祖先枚举到,但是上界依然是\(O(n^2)\)。
不过对于大树比较浅的情况就可以通过了,期望得分 71pts。
我们尝试优化,但是上面做法的本质就是在枚举根的位置,这一步我暂时无法省略,所以无法通过本题。
换一个思路,加入行不通,我们考虑删除。
发现计算方案数并不好算,因为每并上一棵子树都需要去乘上组合数。考虑计算概率,这样的好处是对于每一棵子树概率独立,最后乘上总方案数即可。
设 \(f_x\) 为删完x子树,得到的排列是合法排列的概率。
一个朴素的做法是枚举最后一个删除的是谁。
\(f_x=\frac{1}{siz_x}\times (\prod_{v\in son_x}f_v+\sum_{v\in son_x}\frac{siz[v]}{siz[x]}f_v\times \prod_{k\in son_x \ and\ k!=v}f_k)\)
这样计算复杂度为\(O(nlogn)\)。期望得分 100pts。
把式子拆开,可以得到一个 \(O(n)\)的算法,但是拓展意义不大,所以就不多说了。
B - 密码锁
这个我只会暴搜啦。没什么好说的。
首先这个图是个竞赛图,缩点以后是条链,我们需要计算强联通分量的个数,实际上可以转化为计算割集的个数。所谓割集就是对于一个集合S,集合内部和外部的连边都是指向外边的。
暴搜计算可以得到 49pts。
根据期望的线性性,我们可以单独算出每个集合对应的概率,然后加起来就是期望。
看到大部分边权都是0.5,只有小部分是特殊的。于是我们可以分集合大小计算期望,只计算特殊边,剩下的最后乘上去即可。
考虑到特殊边的边数是不一定的,所以我们给每个特殊边的贡献乘上2,最后乘0.5,这样特殊边的边数就没有影响了。
一个朴素的思路是枚举哪些边有贡献,显然这些边的两端不在一个集合,其余没贡献的特殊边的两端在同一个集合。
这样就可以进行黑白染色,然后缩成很多个联通块,最后做一遍背包往集合里面加点就行。
复杂度 \(O(2^m\times n^2)\)。这个复杂度有点高,平颈在于背包。
观察到我们实际上是在把联通块当成物品来做背包,那么可以分开计算每个联通块的贡献。
具体的,枚举每个联通块中在割集中的是那些点,然后加上贡献。
最后对于每一个联通块,分贡献点数的多少往上做背包即可。
因为只有m条边,所以同一个联通块最多有m+1个点。复杂度 \(O(2^{m+1}+n^2)\)。期望得分 100pts。
C - 笔试
这个UNR的笔试怎么会跑到DP专题里去呢,奇奇怪怪。
D - 梦中的题面
首先观察到每个数的限制是 \(b^i\),同时 n 也和 b 有关。这给我们一个提示是我们可以将n转化为b进制数字,然后进行数位DP。
先来考虑C=1的情况,这样对于每一位 i,有m-i+1个数可以取到任意0——b-1的数字,于是我们可以设计状态,记录当前的位次,以及上一位退下来了多少位。
然后枚举当前一位选择了多少,这里需要一个m-x+1个数不超过b-1加起来总和是i的方案数,这个方案数可以预处理,容斥即可。
由于状态只有\(m^2\)种,枚举时是有mb种值,所以复杂度\(O(m^3b)\)。
注意退下来的数可能很多,但是一旦超过m×2之后,下面的数位就能够任取,因为这时候下面的数加起来不可能比退下来的位还大,所以直接返回对应的方案数即可。
然后考虑c=0的情况,这个时候无非就是每个数可以恰好取到\(b^i\),并且取到之后后面必须取0,那么状态里面增加一维表示已经取了几个数,那么后面这几个就需要取0。
剩下的和c=1一样,复杂度\(O(m^4b)\),需要大力卡常。
E - Make It Ascending
朴素的思路,枚举留下哪些点,然后枚举删掉的数加给了谁,目测能过<=10的点。
枚举留哪些点,然后枚举剩下的点加给了谁,这个过程实际上是在枚举一个一个的集合,然后在集合里选择一个代表点,满足集合排好序后代表点递增并且集合权值递增。
考虑dp,直接装压表示当前选的关键点是谁对应的集合是谁,维护一个最大序列长度和最小结尾就行。转移枚举补集的子集,然后选出一个代表点,这个代表点实际上在满足要求的情况下越小越好。
需要注意的是这种做法只有充分性,可以构造出一组可行解,但是并不是所有状态下一定满足全局最优,也就是没有必要性。
想要必要性的话可以把维护的长度放到状态里,这样dp只需要维护一个最小结尾。复杂度 \(O(n^23^n)\)。
F - Turtle
如果上下两行的数字确定,那么答案是容易确定的,最大值最小一定是将上面从小到大排序,下面从大到小排序。
考虑证明,其实是显然的,因为此时决策点的前缀和后缀都是最小的,换做任何一种排列都会比这个大。
写出来几个数列后你会发现决策点只有1或者n,因为数列上下之差单调递增,所以移动决策点要不就动到最后,要不就不动。
这样问题可以转化为将2×n个数划分成两个大小为n的集合,使得(集合的权值和加上另一个集合的最小值)的最大值最小。
考虑最小值取到谁,一个一定是全局最小值,另一个一定是全局次小值。因为不论怎么安排路径,最后一定经过起点和终点,将这两个最小的数填上去一定最优。
那么问题变成了选择一半的数使得两个集合的差值最小,这个可以用背包来做。
复杂度\(O(n^2a)\)。
我想的比较浅,最后没有写背包,直接随机化rand出了集合里的元素。