题5
模拟赛的题(不知道还能不能打开)
大意:给定一个排列,支持的操作是选定一个区间\([l,r]\)把其挪到最前面或最后面,定义\(f_{i}\)表示将这个排列变成没有\(i\)组成的逆序对的排列所需要的最少的操作次数。询问\(\sum_{i=1}^{n}f_i\le m\)的长度为\(n\)的排列数。
首先考虑\(f_{i}\)如何求。我们把大于\(i\)的设为\(1\),小于\(i\)的设为\(-1\),那么相当于操作需要把\(1\)都挪到右边,\(-1\)挪到左边。考虑一个操作\([l,r]\)如果包含\(i\),那么这个操作其实可以由\([1,l-1]\)或\([r+1,n]\)替代。因为我们只关心\(1,-1\)和\(i\)的关系。那么我们就把操作都变成了与\(i\)无交的操作了。那么此时答案其实就是\(i\)左边\(1\)组成的连续段个数,和\(i\)右边\(-1\)组成的连续段个数了。那么我们令\(p_0=-1,p_{n+1}=1\),那么我们要对\([-1,1]\)这样的东西进行计数,由于我们要求\(\sum\),所以考虑拆贡献。考虑一个\(i\)位置,什么时候\(i=-1,i+1=1\),即当枚举的\(x\in(p_i,p_{i+1})\)。所以这个位置对答案的贡献就是\(\max(p_{i+1}-p_i-1,0)\),对于\(p_0,p_{n+1}\)呢?将\(p_{0}=0,p_{n+1}=n+1\)即可。那么我们就是对\(\sum_{i=0}^{n}\max(a_{i+1}-a_i-1,0)\le m\)这样的排列进行计数。那么就是连续段\(dp\)进行解决了。
模拟赛
大意:\(n\)个在\([1,m]\)中均匀随机的数组成的\(a\)序列,求出其众数出现的次数的期望
定义\(f_i\)为\(i\)在序列\(a\)中出现的次数,那么众数的次数就是\(\max_i f_i\)。我们考虑\(Min-max\)容斥(想到的是神人)。对\(f_i\)进行容斥。由于\(f_{i}\)的下标的值,所以全集就是\(U={1,2,\dots,m}\),\(\max_i f_i=\sum_{S\subseteq U}(-1)^{|S|+1}\min_{j\in S}f_j\)。我们是不关心具体的值的,我们只关心\(|S|\)也就是出现的不同数的种类。设出现了\(k\)个数,每个数出现的次数为\(a_i\)那么就可以变成\(\sum_{k=1}^{n}(-1)^{k+1}A(m,k)\frac{n!}{\prod a_i!}\min_j f_j\)。然后我们考虑求出\(\frac{n!}{\prod a_i!}\min_j f_j\)。由于直接等于太困难,我们先求出\(\min\ge k\)的值,然后差分。使用\(dp\)求解。我们从大到小放出现的次数\(a\),设\(f_{k,i,j}\)表示放了次数\(\ge k\)的数,出现了\(i\)种不同的数,一共放了\(j\)个数的方案。然后转移就转。是\(O(n^3)\)的,然后常数比较小,就过了(狗头)
补:其实根本不用容斥的,我们直接求\(A(m,k)\frac{n!}{\prod a_i!}\max a_i\)。然后还是从大到小放数,那么我们放的第一个就是\(\max\)。所以我们只需要在\(i\)由\(0\to >1\)的时候把\(a\)的贡献加上就行,转移一样,最后就不用差分了。想题的时候一定要想简单一点。(用容斥反倒复杂了)
QOJ6669
拉插,然后存一下系数。\(nb\)题目。
总结:把两个值压缩成一个值可以通过拉插然后记录系数。还需要知道次数。
插播一下拉插:给定\(n\)个点\((x_i,y_i)\),确定一个\(n-1\)次多项式,式子就是\(f(x)=\sum_{i}^{n}y_i\prod_{j\ne i}\frac{x-x_j}{x_i-x_j}\)。正确性就是带入一个\(x_p\),当\(i=p\)时,后面的连乘等于\(1\),否则会出现\(x_i-x_i\)而导致连乘变成\(0\)。然后写的话就是多项式乘法和多项式除法做到\(O(n^2)\)。
P9523 [JOIST 2022] 复制粘贴 3 / Copy and Paste 3
考虑答案是怎样构成的。一开始想的是先处理一段后缀,然后粘贴到\(Y\),然后在放一段前缀,然后把\(Y\)放到后面,但是这样不对。因为只是用了一次粘贴,这样肯定不是最优的。考虑如何使用多次。如果\(S\)中有一个串\(x\)出现了多次,那么我们可以先造出来\(x\),然后在粘贴,然后把不是\(x\)的用操作一,碰到\(x\)用操作三。我们发现这样就覆盖了所有可能的策略。考虑实现。设\(f_{i,j}\)表示拼出串\(S_{i\sim j}\)需要的最小操作次数。首先,基本的,我们可以由\(f_{i+1,j}+A,f_{i,j-1}+A\)转移过来。接着我们考虑,不是这两种操作的还有什么。即一开始就用粘贴操作。也就是存在一个\(x\)即使\(x\)的前缀,也是\(x\)的后缀。那么我们枚举一个\(S_{i,k}\),满足这个也是\(S_{i,j}\)的后缀,然后计算中间有多少个不重叠的\(x\)。然后计算贡献。实际实现的时候,我们枚举\(l,i\),然后枚举下一个\(S_{l,i}\)出现的位置,在位置的末尾\(r\)处更新\(f_{l,r}\)。考虑,复杂度最爆炸的时候就是全\(a\)的时候,此时考虑每个点作为结束节点的次数,就是\(\sum_{len}^{n}{\frac{n}{len}}\),那么就是\(O(n^2\log n)\)。而找下一个出现次数的可以做到\(O(n^2)\)。
总结:考虑答案是怎样的形态,抽离出可以包含所有情况的模型,然后通过这个进行转移。
P7620 CF1431J Zero-XOR Array
这个题很厉害!主要思想就是找到在所有数中,第一个出现的不压上界和不压下界的位置,注意我们找的是所有数中的,加入有两个数\(a_i,a_j\),他们第一个不压上下界的位置是\(k_i,k_j\),我们看的是\(k_i,k_j\)的大小而不是\(i,j\)的大小。这点很重要。如果出现了,一个最早的位置\(k\)不压上下界,根据定义,所有数的所有前\(k-1\)位都是压着上界或下界的。而对于其他数,我们设他确定了第\(k\)位即以前的数后后面的位置还有多少选择\(x_i\)。那么答案就是其他数的\(x_i\)乘起来。因为由于当前数已经不压上下界了,所以相当于后面的位置可以随意的选择,所以无论其他的怎么变都可以使得答案为\(0\)。到这里,题目基本解决了。实现上,我们考虑枚举第一个出现的位置,那么前面的位置不是压上界就是压下界,总共会有\(2^{n-1}\)种情况,我们先判断这些情况是否是合法即能不能让前\(k\)位异或和为\(0\)。如果合法,我们对第\(k\)位每个数是\(0\)还是\(1\)进行\(dp\)。就是说,我们设\(f_{0/1,0/1}\)表示现在到当前这一位的异或和位\(0/1\),是否出现过了不压上下界的点的总方案数。然后转移需要分讨,比较麻烦,需要在脑子清醒的时候写。但是是一道好题!!
总结:在数位\(dp\)过程中,如果出现了一个不压上下界的位置,那么后面的位置都可以随便填了,这个性质很重要!!。在二进制中,一个可以随便填的数就能操控最终的异或和了。
P6097 【模板】子集卷积
考虑\(i\cap j=0\)的限制怎么翻译。就是\(|i|+|j|=|i\cup j|\)。那么我们设\(F_{i,j}=|j|==i?f_j:0\)。那么我们枚举\(popcount\),然后把他拆成两个相加,具体的就是\(C_{i,j}=\sum_{i_1+i_2=i}\sum_{a|b=j}FA_{i_1,a}\times FB_{i_2,b}\)。可以看到,后面的内一项就是\(FA_{i_1}\oplus FB_{i_2}\),就是或卷积。或卷积完了后我们需要把\(C\)中\(|j|\ne i\)的位置赋为\(0\),就是不合法。这样我们就得到了\(O(2^nn^2)\)的做法。
总结:\(i\cap j=0\)可以翻译成\(|i|+|j|=|i\cup j|\)
sez885
咋说呢,感觉脑子成屎了还想用\(bitset\)。。就是枚举一个\(ans\),大于等于他的是\(1\),其余为\(0\),然后维护一个数组\(b[i][j]\)表示\(i,j\)都是\(1\)的在前面行有没有出现过。看起来挺爆的,但是每个二元组只会被放一次,所以只有\(n^2\)次操作,所以复杂度就对了。
总结:对于一些有两个就行的东西,每一个只会被扫到一边,所以复杂度应当算每个只扫一遍的。。
P5356 [Ynoi Easy Round 2017] 由乃打扑克
这个看了些提示,感觉分块太不熟练了。
就是分块,对每一个块维护从小到大排序后的数组,还有\(lazy_tag\)。对整块加不影响大小关系所以直接加到\(tag\)上,散块上暴力重构。修改操作就这样。查询比较重要应该感觉忘了。因为要查询排名是第\(k\)的数,一般有两个,一个是维护至于选段书然后线段树二分,另一个是二分枚举一个值找数组里有多少小于和小于等于他的数。由于这个题是区间查询区间修改所以第一种方法就不行了。所以采用第二种,对于散块就暴力找,整块上就使用排序后的数组去找。然后就没了。复杂度\(O(m\sqrt{n}\log V\log n)\)卡常。
卡常技巧:\(lower_bound\)时,特判一下和第一个和最后一个的大小关系。\(upper_bound\)也是。
总结:查询第\(k\)小的时候:1. 维护值域线段树进行线段树二分。2. 二分一个数寻找有多少个比它小的数,为了寻找这个并且避开权值线段树,采用将数组分段排序的做法。
P3863 序列
这个思路还没见过。首先我们可以把每个查询的位置\(y-a_p\),这样就相当于所有的\(a\)初始为\(0\),这个很重要,因为这样就保证了,所有\(a\)的初始状态相同。我们考虑这个时间维的问题,他对时间大于的操作都会有影响,也就是对一个时间后缀进行操作。对于一个修改操作\([l,r]\),我们可以把它拆成两个操作\([l,n],[r+1,n]\)。所以现在我们只需要对后缀进行操作了。我们考虑如果有一个横轴是时间,纵轴是那个数组的矩阵,\((t,i)\)表示时间\(t\)时\(a_i\)的值是什么。那么修改就是对一个右上角的矩形进行统一改。维护矩阵的信息我们就尝试使用扫描线。那么查询呢,\(t\)时刻的查询\(i,x\)就是找\(a_i\)内一行前\(t\)个中大于等于\(x\)的个数。所以我们需要知道横行的消息。所以我们需要维护时间轴,所以我们就在序列轴进行扫描线。具体的,我们在每个位置挂上询问和修改,修改的话,由于要维护比\(x\)小的值,所以我们借用上一个题的思路,就做完了。
总结:有关时间的序列操作问题,可以画出时间-元素图,查看是否能够转化成对一个矩阵进行操作,如果可以,尝试使用扫描线,扫哪一维需要根据查询的类型确定,比如这个,我们维护不了矩形中小于等于\(x\)的个数,但是能维护一个序列,所以以元素为扫描线。
P5268 [SNOI2017] 一个简单的询问
把答案差分一下就行了。\((get(1,r1)-get(1,l1-1))*(get(1,r2)-get(1,l2-1))\)然后拆成四个,这样就直接用莫队维护了。
P4689 [Ynoi Easy Round 2016] 这是我自己的发明
上面内阁题的强化版,就是把树转到\(dfn\)做,然后根的问题就使用两个树相减就行了。
注意:如果用\(Ans_i=-1\)来标记这个是否是查询操作,这样是不太对的,因为第二个题会有预处理的部分,所以可能恰好变成\(-1\),这样就错了。喜提调一晚上。\(GPT\)牛逼。
P4590 [TJOI2018] 游园会
\(dp of dp\) 的板子。设奖章串为\(A\),我们首先考虑兑奖串\(B\)确定时如何求最长序列,\(f_{i,j}\)表示\(A,B\)分别匹配到\(i,j\)时的最长子序列,\(f_{i,j}=\max(f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+[A_i==B_j])\)。现在\(B\)是随机的,先不考虑\(NOI\)的限制。根据上面的式子,我们发现,我们想要求出\(j+1\)的所有数组,我们只需要知道\(f_{1\sim k, j}\)即可。于是我们考虑把这个东西放到状态里,设\(g_{i,S}\)表示枚举到第\(i\)位的时候,\(f_{1\sim k, i}\)压成了\(S\)时的方案数是多少。那么转移就好转移了,就是转移中套一个转移。但是我们发现压的时候很难受,太大了。接下来到了这个题关键的时候,就是如何压。针对最大子序列下手。有两个性质:1. 递增2. 相邻两个至多差\(1\)。于是我们可以直接记差值。也就变成了\(2^k\)个状态可以接受。至于\(NOI\)的限制,多加两维限制一下就行。
总结:若要压一个序列,考虑这个序列是否是特殊的,如单调,相邻两个差值很小,个数比较少。
P10107 [GDKOI2023 提高组] 树
确实是好题。
如果是一个\((i,i+1)\)这样的一条链,可以倍增(想到是不可能想到的)。就是维护\(f_{i,j}\)表示\([i,i+2^j-1]\)部分的答案是多少。合并\([i,i+2^{j-1}-1],[i+2^{j-1},i+2^{j}-1]\)。考虑,\(f_{i,j-1}\)的答案是对的,直接加就行了。但是后面呢?关注贡献形式,相当于是\(v\oplus (dis+2^{j-1})\)。问题在于\(2^{j-1}\)怎么办,因为如果进位的话会很麻烦。但是我们考虑,\(dis\)不可能\(\ge 2^{j-1}\),因为是在\([i+2^{j-1},i+2^{j}-1]\)区间内与\(i+2^{j-1}\)的距离。所以加\(2^{j-1}\)想到与\(\oplus 2^{j-1}\)。那就好办了啊,我们差分一下维护\(l,r\)区间内第\(j\)位为\(0,1\)的个数就能做了。怎么转到树上,使用长链剖分优化一下,复杂度\(O(n\log n)\)。
注意:转到树上的时候,我们维护\(g_{i}\)表示从\(i\)到其所在长链末尾的\(0/1\)个数,如果维护到根的,那么我们维护正确的信息复杂度就错了。而这样维护可以通过类似懒标记的方法把修改传递上去。
总结:1. 树可以先转到链上做。2. 加一个\(2^{j}\)有时可以变成$\oplus $。3. 差分既可以维护前缀也可以维护后缀。
sez893
这个,分块做法比较好想,就是块的大小为\(B\),维护当前块内的回文串数量和被什么所覆盖,然后就暴力,边界也暴力。使用马拉车判回文这样,取块为\(\sqrt{mk}\),卡飞了。优化是因为覆盖一个区间,所以是用线段树维护,线段树的节点是每个块的编号,然后复杂度就是 \(\log{\frac{n}{B}}k\),应该可以过(代码先咕一咕)。。
sez888
这个挺神的。就是把序列按照\(k-1\)分块,这样每一个查询系列就能恰好被分成一个块的前缀和一个快的后缀。这些是好维护的。好题。
总结:若询问区间长度固定,则可以分块把询问区间分成两个前缀和后缀
P6189 [NOI Online #1 入门组] 跑步
这个算是个典题把,就是小于\(\sqrt{n}\)的数用完全背包做,\(>\sqrt{n}\)的数一定不会出现\(>\sqrt{n}\)次,所以就\(f_{i,j}\)表示用\(j\)个数表示出\(i\)的方案数,\(f_{i,j}\to f_{i+\sqrt{n}+1,j+1},f_{i,j}\to f_{i+j,j}\)。分别对应着新加一个数,和全局加\(1\)。时间复杂度\(n\sqrt{n}\)
P8340 [AHOI2022] 山河重整
这个感觉好困难啊。。。
先介绍一个数划分的方法。当每个数只能使用一次的时候,我们有性质是最多只会出现\(\sqrt{n}\)个数。所以我们换一种枚举方式。就是我们把最后的集合的数从小到大排序,然后每一行每一行加。就是我们把值看作下标,把小标看着值。例如\(4,3,1\),变成\(3,2,2,1\)。这样。我们相当于有了\(1\sim \sqrt{n}\)这些物品,然后\(i\)出现则\(1\sim i-1\)都要出现,做完全背包。所以我们只需要从大到小枚举物品,枚举到一个物品先给前面的所有方案都加上该物品,在用这个物品跑完全背包。
首先看原题意的充要条件:\(\forall k\in [1,n],\sum_{x\le k,x\in S}\ge k\)。易证,考虑\(dp\)过程即可。然后考虑容斥掉不合法的情况。我们在第一个不合法的位置进行容斥,由于是第一个不合法的,即第一个满足\(\sum_{x\le k}<k\),所以\(\sum_{x\le k-1, x\in S}=k-1,k\notin S\)。这个是个很好的性质。但是容斥系数怎么去。我们想要枚举第一个不合法的位置,就要求前面都合法并且\(1\sim i\)中的数和恰好为\(i\)。所以我们设\(f_{i}\)表示\(1\sim i\)中有多少子集使得\(1\sim i\)都合法并且子集和为\(i\)。那么答案就是\(2^n-\sum_{i=0}^{n-1}f_{0}\times 2^{n-i-1}\)。问题转化为怎么求\(f_{i}\)。我们已经考虑容斥,总方案就是\(1\sim i\)表示出来为\(i\)的方案数设为\(h_i\)。这个是划分数。但是这个题中每个数只能用一次,所以我们在这里使用一个其他的办法,和正解相关。就是上面提到的数的划分方法。继续往下考虑。得到式子\(f_{i}=h_i-\sum_{j=1}^{i}f_j\times h(j+2\sim i, i-j)\),其中\(h(j+2\sim i, i-j)\)表示用\(j+2\sim i\)中的数表示出\(i-j\)的方案数。我们考虑如何对每个\(i\)都求出\(g_i=\sum_{j=1}^{i}f_j\times h(j+2\sim i, i-j)\)。这个就涉及到刚刚的内阁数划分方法了。就是普通做数的划分,就是个数从小到大枚举。这个由于有限制的范围,所以我们可以在一开始加入物品的时候先把所有的就加上,例如,若一共有\(i\)个物品被加入,并且起点是\(j\),那么加入\(j+(j+2)*i+i\)。然后在做背包,相当于给每个方案加入了一个\(j+(j+2)*i\)的偏移量。然后贡献呢?数的划分,我们记录方案,所以贡献相当于是\(1\)。但是这里不是方案,二十值,所以贡献就变成了\(f_{j}\)。就是说,我会加入\(j+(j+2)*i+i,i\in [1,\sqrt{n}]\)这些物品,这些物品的初始权值都是\(f_j\),而能通过多少种方案到达\(k\),\(f_{j}\)就被加了多少次,所以就是\(g_k\)了。所以这样就是对的。但是复杂度还是\(O(n^2)\)的。咋办。此时我们关注到当\(j+2\sim i\)能拼出来\(i\),说明\(j+j+2\le i\),所以我们要求\(i\),只需要知道\(1\sim \frac{i}{2}\)就行了。所以我们可以直接分治做。就是先求出\(1\sim 2^x\),再求出\(2^x+1,2^{x+1}\)。那么\(n\sqrt{n}\log n\)就这样了。
总结:当数只能用一次时,最多会出现\(\sqrt{n}\)个数,这是我们把行和列换一下就能做到\(n\sqrt{n}\)了。在这种方法中,我们可以灵活的改变起点,只要区分出来权值就行了。好题。
注意:这样的数的划分,最大值取\(\sqrt{n*2}\),因为\(x(x+1)=2n\),所以\(x\)取\(\sqrt{2*n}\)不能取更小的。
Loj6515. 「雅礼集训 2018 Day10」贪玩蓝月
这个比较有意思。他不允许我们撤销了。但是\(p\)很小。有操作和删除操作,所以我们能得到每个数处在的时间范围是什么,那么我们建线段树节点时时间点,然后把每个数存在的时间上打上\(tag\),然后类似缺一分治内样,从根节点到叶子节点,如果有点就加上,单次加复杂度是\(p\)的,每个数最多被加\(\log n\)次,复杂度是\(np\log n\)的。比较巧妙。
总结:遇到加入和删除操作,可以利用时间轴,表示每个数出现的时间范围是什么,在时间轴上做。如果可撤销就直接撤销就行。
P6326 Shopping
树形背包+点分治树形背包+二进制分组背包。感觉很好的一道题啊。
我们先来介绍一种不一样的根联通树形背包的做法。先跑出\(dfs\)序。由于\(dfs\)序保证了子树里面编号是连续的。我们设\(f_{i,j}\)表示考虑了\(dfs\)序为\(i\sim n\)的点,背包和为\(j\)获得的最大收益。有一个\(i\),不选\(i\),那么\(i\)子树里都不能选,所以由\(f_{i+sz_{seg_i}}\)复制过来。如果选,那么我们从\(f_{i+1}\)和\(i\)做一个背包就行了。考虑\(i+1\)不是到他的儿子,就是他是叶子到了下一个子树的根节点,转移都是合法的。而这个复杂度的,因为我们只需要求\(rt\)的值,所以复杂度就是\(O(nm)\)的。有点好看的做法。\(dfs\)从前往后也可以,从填表转成刷表就行了。
然后再介绍一下多重背包的优化,二进制分组。我们把每个物品的数量\(d\)拆成\(1,2,4,8,\cdots,2^{k-1},d-(2^k-1)\)。这样\(1\sim k\)中每个数都能被\(0/1\)表示。所以现在变成了\(\log n\)个物品,所以复杂度就是\(O(n\log dm)\)也很好啊。还有一种做法是单调队列优化,就是按照\(%m\)进行分类,每个都开一个单调队列。转移就\(O(1)\)这个复杂度就变成了\(O(nm)\)不过上面内阁就挺优的。
再说这道题。经典的\(trick\)是进行点分治,我们再每个连通块第一个作为分治中心的点出进行求解,就是用上述两个办法。然后就做完了。
总结:板子,进阶例题qoj4815,不过这个也有其他更优做法。
注意:由于\(f_{i+1}\to f_{i}\)的时候,强制\(i\)选了,所以转移的时候先强制都选一个,二进制分组的时候分\(d-1\)就行。复杂度不变。
qoj4815
来说一下这个,这个就是上面内阁dp方式有大用的题目。因为这道题要求的是每个\(x\)所在的连通块的最大权值。我们还是再每个连通块最先成为分治中心的点处统计。但是怎么解决找每个点呢。接下来我们定义左边的树是相对的,就是\(u\)左边的点就是比他先遍历到的点并且不是他祖先的点,右边的是比他后遍历到的点。那么我们考虑,一个点所在的连通块在当前的分治根为\(rt\)的部分怎么表示。可以拆成\(3\)部分。\(u\to rt\),\(u\)左边点,\(u\)右边的点。我们考虑,\(u\)右面的点的\(dfs\)序是一段后缀对吧,这就很好,因为我们的\(dp\)是从后往前的。但是左边的树呢?他不是连续的,会断。不过没关系,我们倒着\(dfs\)一遍就行了,就是先遍历\(u\)的最后一个儿子。这样的到了两个\(dp\)数组。我们只需要在\(x\)处,挑出两个后缀,加上\(u\to rt\),然后使用\(O(k)\)的时间复杂度找到和为\(k\)权值最大的方案。
总结:\(u\)所在连通块可以分成\(3\)个部分,\(u\to rt\),\(u\)左边的树,\(u\)右边的树。
P9197 [JOI Open 2016] 摩天大楼 / Skyscraper
这个是连续段\(dp\)的板子。思路和一般的连续段\(dp\)差不多,需要分讨
P2522 [HAOI2011] Problem b
莫比乌斯反演和二维整除分块,\(O(n\sqrt{n})\)
总结:莫比乌斯反演;二维整除分块
Loj6077
这个妙妙题。这个第一步可以想到就是,考虑每个位置能产生的贡献其实与其前面的无关,只和其位置有关。第\(k\)个位置能产生\(0,\dots,k-1\)。所以可以写成一个多项式\(1\times (1+x)\times (1+x+x^2)\times\dots\times(1+x+\dots+x^{n-1})\)。那就可以写成\(\frac{\prod_{i=1}^{n}(1-x^i)}{(1-x)^n}\)。我们先看下面这个,因为有分式所以很烦啊。但是\(\frac{1}{1-x}\)是\(\sum_{i=0}x_i\)的封闭形式,然后这两个就是等价的,证明不会。那么下面内阁第\(k\)项的系数就好说了啊,相当于把\(k\)分成\(n\)个\(\ge 0\)的整数。然后这个插板法就做了。然后上面内阁怎么算。其实我们可以看作有\(1\sim n\)个数,问拼出来\(1\sim k\)之中的数的方案数,数不重复。我们先考虑一下如果\(k\le n\)时。如果有重复那么就直接数的拆分做。没有重复带来一个性质是一定不会有\(>\sqrt{n}\)个数。回想一下数的拆分的算法流程。给全集加\(1\),新加一个\(1\)。没有重复的数的性质等价于没有两个连续的操作是新加一个\(1\),换句话说就是新加一个\(1\)后一定跟着给全集加\(1\)(末尾操作除外)。那么我们直接强制做这个操作就行了。统计答案的时候加上末尾是\(1\)的贡献就行了。如果\(k>n\)的时候呢?直接做可能会有\(>n\)的数放到方案里,我们需要剪掉这种方案。观察上面的两个操作,最多会给最大值加\(1\),所以第一个不合法的时候最大值是\(n+1\)(废话)。并且只有一个。设\(h_{i,j}\)表示用\(j\)个数拼成\(i\)的合法方案数。所以现在还没确定的方案只能由合法方案转移过来,所以不合法也是第一个不合法。所以只需要\(h_{i,j}-=h_{i-n-1,j-1}\)。注意:这个只需要减一次,所以在遍历到的时候写就行。
有点模板。
for(int j = 0; j <= B; j ++) {
for(int i = 0; i <= k; i ++) {
if(!h[i][j]) continue;
if(i > n) h[i][j] = dec(h[i][j], h[i - n - 1][j - 1]);
if(j) {
h[i + j][j] = Add(h[i + j][j], h[i][j]);
}
h[i + j + 2][j + 1] = Add(h[i + j + 2][j + 1], h[i][j]);
}
}
f[0] = 1;
for(int i = 1; i <= k; i ++)
for(int j = 1; j <= B; j ++) {
int x = (j & 1) ? -1 : 1, y = (h[i][j] + h[i - 1][j - 1]) % mod;
f[i] = Add(f[i], (x == -1) ? mod - y : y);
}
CF1781F Bracket Insertion
计数好题。本质上还是计数啊。
括号问题一般会转成\(\pm 1\)做。这倒也是。但是这个题的操作没见过。不过转化一下能转化成挑一个位置设其前缀和为\(x\)把其后面加上\(x+1,x\)或\(x-1,x\)问最后所有数非负的方案。所以问题就是原来集合\(S=\left\{ 0\right\}\),每次选择\(x\in S\),加入\(x+1,x\)或\(x-1,x\)。问最后非负的方案数。但是接下来就没思路了。(观摩题解)根据这个转化题意我们能找到一个状态的设法\(f_{x,i}\)表示\(S\)初始是\(x\),进行\(i\)此操作后合法的方案数。答案就是\(f_{0,n}\),然后根据题除以总方案数。考虑怎么转移,我们考虑进行一次操作,由于第一次,所以只有两个选择。1. \(S\to \left\{ x_1,x+1,x_2\right\}\),此时我们把操作分为三类,由\(x_1,x+1,x_2\)分别拓展出来的。那么贡献为\(f_{x,n}=p\sum_{i=0}^{n-1}\sum_{j=0}^{n-1-i}f_{x,i}f_{x+1,j}f_{x,n-1-i-j}\times \binom{n-1}{i}\binom{n-1-i}{j},\)。2. \(S\to \left\{ x_1,x-1,x_2\right\}\),转移同理。此时我们得到一个\(O(n^4)\)的做法。调换一下枚举顺序,先枚举\(n-1-i-j\),然后再用前缀和优化一下就行了。好题;
总结:不知道怎么设状态的时候,可以尝试把问题转化一下,将起始状态或终止状态或中间状态放到状态里,(防止爆掉)也可以是转移过程(dp of dp);进行操作型的\(dp\)不知道该怎么转移的时候可以考虑先操作一步,得到新的状态后再做,就像期望的\(dp\)一样,从后往前进行\(dp\)。
CF1305F Kuroni and the Punishment
--随机化题--
就是答案一定不超过\(n\),因为把每个数变成偶数就行。有这个性质推的,一定有一半以上数不变或加一或减一,否则不满足上述性质。那么我们随机一个数,不变加一减一,然后分解,找答案,对的概率是\(\frac{1}{2}\)。多随机几遍就行了。
。。。。
CF13C Sequence
这个有点神奇不过也挺有意思的。就是要关注到最后序列的数组成的集合一定是原来序列数组成的集合的子集。就是,考虑如果最后变成了一个数,那么通过调整法我们知道一定是变成原序列的一个数是最优的。然后如果变成了两个数,三个数就是归纳一下,但是有点感性不过写了确实对了。。。这个做法\(O(n^2)\)。还有更厉害的是下面的加强版。
P4597 序列 sequence
这个就是加强版。对于每一个\(j\),\(f_{i,j}\)的\(i\)都是下凸的。这个使用\(slope trick\)。因为要维护前缀最小值,所以线段的斜率最多维护到\(0\)。然后我们维护斜率拐点的集合,就是一个点代表斜率\(+1\)例如,\(()1-2,-2),(2-3,0)\),那么在集合里加入两个\(2\)。然后这个题目实际上是若干个绝对值函数加起来\(\sum{|x-a_i|}\),所以加入一个\(|x-a_i|\)这样的函数,相当于加入两个拐点\(a_i\),因为绝对值函数在\(a_i\)处的斜率变化为\(2\),下凸函数是可叠加的(别的其实也能叠加)。由于我们维护的是下凸的函数的前缀\(\min\),所以我们不需要维护斜率大于\(0\)的线段。我们考虑,加入\(a_i\),在\(a_i\)右边的线段的斜率都\(+1\),所以最大的拐点右边的线段都从\(0\to 1\)了,所以我们需要把最后一个拐点删去,这样就行了。然后删和加的过程中,把斜率为\(0\)的线段左端点的答案维护一下。就能做了。
for(int i = 1; i <= n; i ++) {
ll x;cin >> x;
q.push(x);q.push(x);// 加入两个拐点
ans += q.top() - x;// 这个是维护最小值现在的值,因为最大的拐点右边就是斜率为$0$的线段,所以给后面的加$1$后,这个点的左边就恰好是斜率为$0$的线段,也就是他恰好还是最小值。所以加上多余的贡献
q.pop();// 由于这个点的左边恰好是斜率为$0$的线段,所以我们要找左边第一个左边不是斜率为$0$的线段的点,否则下次再$+1$他就不是最小值了,而是前面是最小值。
}
总结:遇到多个凸函数相加的题目,能得到新函数还是凸函数,此时可以用\(slope trick\)优化复杂度。
CF713C Sonya and Problem Without a Legend
上面的经验,主要想说,让序列变成严格单增转成不严格单增的方法是先给\(a_i-i\)。
P10013 [集训队互测 2023] Tree Topological Order Counting
这个感觉是一个比较传统的\(dp\),就是设\(f_{i,j}\)表示考虑了除了\(i\)的儿子的其他所有点,\(i\)排第\(j\)的方案数。从父亲节点转移到儿子。注意:这里的定义,是相当于把\(i\)的儿子都删掉,而不是预留出他的位置,第二种\(dp\)是错的(可是我推了两个小时)。初步转移能出来一个\(n^3\)的,然后观察一下能前缀和优化就变成\(n^2\)了(然鹅由于第一个的影响,我以为像第一个优化不了。。。)
总结:写状态的时候想好除了的含义是删掉做子问题还是预留出位置;前缀和优化\(dp\)比较常见,一般遇到需要一个前缀和的都要用前缀和优化。
sez596
这个主要有一个主要的性质没有关注到。就是\(a_1+a_2,a_1+a_3\)都是确定的,我们只需要知道\(a_2+a_3\)就能得到\(a_1\)了,然后就能在\(n^2\log n\)的复杂度内找出所有数。但是\(a_2+a_3\)直接枚举是\(V\)的。但是有一个性质就是\(a_2+a_3\)的排名在\(3\sim n+1\)内。所以只有\(O(n)\)个取值,也就只有\(O(n)\)个\(a_1\)。那复杂度就是\(O(n^3\log n)\)了。
总结:减少\(check\)的次数,关键要找不同的取值次数。而找这个要结合已知信息,找需要什么未知信息,找未知信息的取值个数。(对于不确定的问题这类处理的还是不是很好啊)
sez918
期望题。首先这个在第几轮死可以看成有多少人死在\(1\)前面\(+1\)。而对于每一个\(i\)来说,死在\(1\)前面的概率就是\(\frac{w_i}{w_1+w_i}\)。为啥呢。因为考虑把\(1,i\)合成一个点,对于他们两个来说,\(i\)在\(1\)前面的概率就是前面内个,而其他的点,他们死的顺序不会影响这个点,就是他们组成的事件集合发生的概率是\(1\),所以整体看就是前面提到的概率。而一个人在前面就产生\(1\)的贡献,所以答案就是\(1+\sum{p_i}\),\(p_i\)表示\(i\)死在\(1\)前面的概率。
总结:期望概率题做得太少。
sez600
这个题挺有意思的。就是换两个对序列产生的影响是奇偶性不变,那么必要条件就是逆序对数是偶数。考虑是否是充分条件,这就构造了,就是构造方案。考虑一个排列的情况,就是我们找到当前没有回到应在的位置的最小数,把他和他后面的内个数连在一块,放到最小数应在的位置(在最后的位置就先挪到前面,不能移到前面当且仅当是\(n,n-1\)这样的东西,但是这样就违背了前面的条件)。所以充分必要。然后考虑如果不是排列,有重复元素的话,我们把重复元素按照一定顺序标上大小关系就行了。此时一定可以通过改变重复元素的最后一个元素来改变逆序对的奇偶性,所以就一定是好的。所以不好的是逆序对为奇数的排列。然后考虑这个怎么求。啊这个就是技巧类的把就是(积和式-行列式)/2。由于这个矩阵有点特殊,每行恰好前\(i+1\)位有值,积和式可以\(dpO(n^2)\)求出。行列式可以让\(i=n-1\to 1\)第\(i\)行-\(\frac{w_{i,i+1}}{w_{i+1,i+1}}\)倍的第\(i+1\)行,这样就能变成一个下三角矩阵,此时的行列式就是对角线乘积了。
总结:要对一个不确定的序列计数,我们可以先找到一个必要条件,然后证明他是充分的,或者直接在必要条件的情况下向下\(dp\),看这个情况下有多少合法的;每行只有前\(i+1\)有值的矩阵,行列式和积和式都能在\(O(n^2)\)中求出。
CF1810G The Maximum Prefix
这个题和下面内个题分别体现了两个经典好用的优化\(dp\)方式。有点像。先说这个。本来设\(f_{i,j}\)表示长度为\(i\)的前缀最大前缀和恰好在\(i\)处取到,为\(j\)的概率,但是转移是\(O(n^3)\)的并且无法优化。由于有前缀最大值,我们关注的是历史上出现的,所以我们可以钦定一个最大值,然后找是否经历过,钦定最大值是\(S\),设\(f_{i,j,0/1}\)表示当前\(i\)距离最大值为\(j\),是否经历过最大值的概率,转移是好说的,初始化就是\(f_{0,S,S==0}=h_S\)。其余都为\(0\)。最后输出\(f_{n,0,1}\)。但是要钦定,还是\(O(n^3)\)。不过,我们发现,对于不同的\(S\),除了初始化不同,转移方式完全相同啊。有这个,我们就能把他们放到一起转移,初始化就\(f_{0,j,j==0}=h_j\)。然后输出还是\(f_{n,0,1}\)。
总结:遇到最大值,最小值,可以考虑钦定,如果是历史上的,就加是否遇到过;无后效性的\(dp\)可以看作一个\(DAG\),如果转移过程和起点无关,那么我们可以把所有起点都加进去一起\(dp\)能够降低复杂度;
CF2018F3 Speedbreaker Counting (Hard Version)
性质I:合法的起点一定是一个区间,这个好证。有了这个,我们钦定\(l,r\)是合法的,计算\(f_{l,r}\)表示钦定\(l,r\)为合法的区间的方案数,然后容斥\(ans_{l,r}=f_{l,r}-f_{l-1,r}-f{l,r+1}+f_{l-1,r+1}\)得恰好。然后考虑怎么判断一个\(a\)中\(l,r\)是否合法。性质II:我们一定可以先走完\(l,r\)。就是考虑,没有走的内个点设为\(k\),必须在\(k\)之前到得点设为\(z\),当前起点为\(j\),那就是\(j\)走到\(z\)才能走到\(k\),那\(k\)当起点的时候就不能走到\(z\)了,所以不满足是一个起点。得证。那么我们考虑在这个性质下怎么走。设当前拓展的区间是\(i,j\),若右边有个\(k\)满足\(a_k=k-i+1\),那么必须往右走,否则向左走。那么移动顺序确定了。考虑计数。设当前钦定得合法区间是\(L,R\),设\(g_{l,r}\)表示从\(L,R\)走到\(l,r\)得填值方案数。首先\(L,R\)中的点的方案就是\(\prod{n-\max(i-L+1,R-i+1)+1}\)。考虑外面。由于要枚举\(k\),所以\(g_{l,r}\to g_{l,k}\)然后乘上中间得系数。还有\(g_{l,r}\to g_{l-1,r}\)。最后输出\(g_{1,n}\),这样是\(O(n^3\times n^2)\)的。进一步优化是考虑能否每次直走一格。那么我们需要知道右边有没有一个\(k\),所以就设\(g_{l,r,0/1}\)表示当前拓展到\(l,r\)右边有没有\(k\)的方案数。那么\(1\)的话就必须向右拓展,然后考虑当前这一位是不是钦定的\(k\),乘上系数。具体的\(g_{l,r,1}\to g_{l,r+1,0},g_{l,r,1}\to g_{l,r+1,1},g_{l,r,0}\to g_{l-1,r,1}, g_{l,r,0}\to g_{l-1,r,0}\)。乘上对应系数,最后要\(g_{1,n,0}\)。\(g_{L,R,0}=g_{L,R,1}=1\),\(O(n^2\times n^2)\)。继续优化,由于转移是固定的,我们其实是找的\((L,R,0)(L,R,1)\)到\(1,n,0\)的所有路径上权值乘积的和,然后到\(1,n\)输出。我们发现其实反过来也可以,就是找\(1,n,0\)到\((L,R,0),(L,R,1)\)的所有路径上权值乘积的和。能这么做的原因感觉是转移和起点无关,都是独立的,并且转化成了路径权值和的问题,所以很对。这样就是\(O(n^2)\)了。然后呢,还能优化,因为我们发现这个状态和转移之和长度有关而和具体的左右端点无关,所以就能做了。
总结:对于不确定序列计数问题,找充要条件,或者找必要条件,然后对着这个做;转移和起点无关的\(dp\),如果有多次,可以尝试把他们放到一起转移,或者转成路径,从终点向起点跑(其实上面内个题费点常数应该也可以从终点跑到起点);钦定钦定;\
CF1975I Mind Bloom
这个题太难了。就是不知道这个具体是怎么想到的。考虑最终只有两种结果,赢或输(全是1的判掉),赢得概率有点难算啊,我们算输的。输的就是全\(0\)的情况。下面先说一些前置东西:1. 由于数组有序,我们可以用下标代替值。2. 当前有\(x+1\)个数,最大值是\(i\),经过几轮操作后最大值变为\(j\),并且共有\(y\)个值。那么\(j-x\)这些数被选中的概率是相等的,也就是\(\frac{y-x}{j-x}\)。接下来进入正题。由于最终全\(0\),所以手牌中的\(\max\)的整体趋势是下降的。那么我们对于一个状态\(i,x\)表示最大值是\(i\)除掉\(i\)还有\(x\)个数这样的状态,\(dp\max< i\)的概率,因为还要转移到其他状态,所以我们还需要知道变换后的数的个数,具体的我们设\(f_{i,x,y}\)表示\((i,x)\)这个状态,第一次随机到\(\max<i\),并且此时的有\(y\)个数的概率。考虑怎么求。啊这个也是感觉很跳跃。就是设\(g_{j,y}\)表示从\(i,x\)这样的状态变成\(j,y\)这样的状态的概率,有\(f_{i,x,y}=g_{i-1,y}\),初始就是\(g_{n,x+a_i}=1\)。转移。我们只关心\(j\ge i-1\)这里的值,因为要求得是\(i-1\)。考虑怎么转移。首先原来的\(x\)个数一定\(\le i-1\)。然后对于一个\(g_{j,y}\)我们要考虑\(j\)是否在这个序列里(原来得\(x\)个数\(\le i-1\),所以不会产生影响),由前面说的前置知识,我们知道在的\(p=\frac{j-x}{y-x}\),如果不在的话\(g_{j-1,y}=(1-p)*g_{j,y}\)。如果在呢?那么我们需要从\(j,y\)这个状态,走到一个\(\max<j\)得状态,也就是\(f_{j,y-1,\cdot}\),也就是\(g_{j-1,z}=\sum{p*g_{j,y}*f_{j,y-1,z}}\)。就是走到内个状态得意思。然后\(j=i\)得时候,就是\(f_{i,x,z}\)这个时候,会有一个转移环,就是\(f_{i,x,y}=B+A*f_{i,x,y}\),不过这个解一下就行了。现在我们在\(O(n^5)\)的情况下求出了所有\(f_{i,x,y}\),考虑怎么求答案。设\(ans_{i,x}\)表示在\(\le i\)的数中,除去原有就有的手牌,又抽了一些牌使得总牌数是\(x\)的情况下,输掉游戏的概率是多少。这个和\(g_{j,y}\)求得方法有点像。考虑若\(c_i=1\),那么一定有\(i\),那么\(ans_{i,x}=\sum{ans_{i-1,y}*f_{i,x-1,y}}\)。如果\(c_i=0\),就是有概率有,有的概率是\(p=\frac{x-s_i}{i-s_i}\),\(s_i\)是原有得手牌。分讨一下,若没有\(ans_{i,x}+=(1-p)*(i-1,x)\),若有\(ans_{i,x}+=\sum{p*ans_{i-1,y}*f_{i,x-1,y}}\)。最后输出\(ans_{n,s_n}\)。那么就求完了。时间复杂度是\(O(n^5)\)。常数小就过了(绷
总结:正难则反;\(dp\)可以分段来求,阶段得划分没什么固定得(观察打法)概率。如果有转移环,尝试解方程。

浙公网安备 33010602011771号