F

Non-equal Neighbours

题目大意,给你一个序列 a1,a2,ana_1, a_2, \cdots a_n,要求你构造出 b1,b2bnb_1, b_2 \cdots b_n
其中 1biai1 \leqslant b_i \leqslant a_i,并且 bibi1b_i \neq b_{i-1}
问构造的方案数,大数取模

分析
考虑构造 bb 数组,对于 b1,b2,b3b_1, b_2, b_3,那么方案数可以容斥得到
我们假定 bi,bi+1b_i, b_{i+1} 之间没有限制,如果违背了假定,即 bi=bi+1b_i = b_{i+1}
那么违背假定的个数记为 cntcnt,违背假定的条件记为 (Pbi=bi+1)(P \mid b_i = b_{i+1})
最后容斥的结果为
(a1a2a3)+(1)1(Pb1=b3)a2+(1)1(Pb1=b2)a3+(1)2(Pb1=b2=b3)(a_1 \cdot a_2 \cdot a_3) + (-1)^1 (P \mid b_1 = b_3)\cdot a_2 + (-1)^1 (P \mid b_1 = b_2) \cdot a_3 + (-1)^2 (P \mid b_1 = b_2 = b_3)

其中 (Pbi=bi+1)(P \mid b_i = b_{i+1}) 的方案数为 min(ai,ai+1)\min(a_i, a_{i+1}),容斥公式的推导,即看违背了几个假定的条件

i=1nai+cnt=1n(1)cnt(Pbj+1=bj+2==bj+cnt)\prod_{i = 1}^{n} a_i + \sum_{cnt = 1}^{n} (-1)^{cnt} \cdot (P \mid b_{j+1} = b_{j+2} = \cdots = b_{j+cnt})

最后三个数的容斥结果为 (a1a2a3)(min(a1,a2))a3(min(a1,a3))a2+min(a1,a2,a3)(a_1 \cdot a_2 \cdot a_3) - (\min(a_1, a_2)) \cdot a_3 - (\min(a_1, a_3)) \cdot a_2 + \min(a_1, a_2, a_3)

算法实现
考虑 dpdp,对于序列 b1,b2,bi1,bi,bnb_1, b_2, \cdots b_{i-1}, b_i, \cdots b_n
对于某个位置 iidp(i)dp(i) 表示在 ii 位置合法的方案数,对于 j[0,i1]j \in [0, i-1],假设违背了某些条件
此时如果有 (Pbj+1=bj+2=bi)(P\mid b_{j+1} = b_{j+2} = \cdots b_i),那么存在转移

dp(i)=j=0i1dp(j)(1)ij1min{aj+1,aj+2,,ai}dp(i) = \sum_{j = 0}^{i-1} dp(j) \cdot (-1)^{i-j-1} \cdot\min\{a_{j+1}, a_{j+2}, \cdots, a_{i}\}

优化dp
大致如上图所示,dp 的过程可以边扫描边维护,不难想到对于 min\min 的部分用单调栈,实际上 dpdp 式子整理如下

dp(i)=(1)ij=0i1(dp(j)(1)j+1)min{aj+1,aj+2,,ai}dp(i) = (-1)^{i} \cdot \sum_{j = 0}^{i-1} \left(dp(j) \cdot (-1)^{j+1}\right) \cdot \min\{a_{j+1}, a_{j+2}, \cdots, a_i\}

用一个前缀和 SSSS 维护已经扫描过的集合的和,即上式中的 \sum 部分,在 i1ii-1 \to i 的过程中

  • 如果 1i1 \to i 均为单调增的,那么以 aia_i 为最小值的区间,只有 ii 这一个元素
    SSSS+dp(i1)(1)iaiSS \leftarrow SS + dp(i-1) \cdot (-1)^{i} \cdot a_i
  • 如果不是的话,考虑用单调栈维护单调递增序列,转为考虑加入元素 aia_i 能够给 SSSS 增加的贡献是多少
    贡献 concon 大致是这样的,SS=(单调栈中ai 的元素的贡献)SS -= (\text{单调栈中} \geqslant a_i \ \text{的元素的贡献})
    然后弹出所有 ai\geqslant a_i 的元素,aia_i 和这些元素也会有贡献,加入到 concon
    然后将 aia_i 放入单调递增的栈中,转为第一种情况,concon 要加上 dp(i1)(1)iaidp(i-1) \cdot (-1)^{i} \cdot a_i
    最后把 concon 加入到 SSSS

比较棘手的是单调栈中 ai\geqslant a_i 的元素,和 aia_i 产生的贡献怎么计算?
对于单调栈中的元素 aja_j,还需要维护以 aja_j 作为最小值的区间,实际上是 [l,r][le(j),j][l, r] \leftarrow [le(j), j]
这样对于弹出的每个元素 xx,以 xx 作为最小值的区间是 [x.l,x.r][x.l, x.r],随着 aia_i 的加入
因为 xaix \geqslant a_i,那么 [x.l,x.r][x.l, x.r] 这部分就以 aia_i 作为最小值了
只要维护关于 dp(i1)(1)idp(i-1) \cdot (-1)^{i} 的前缀和,那么 concon 加上 ai(S(x.r)S(x.l1))a_i \cdot (S(x.r)-S(x.l-1)) 即可
弹出栈的时候,更新 le(i)=x.lle(i) = x.l,即记 le(i)le(i) 为最左边的 ai\geqslant a_i 的元素位置

CF759F

最后把 aia_i 加入单调栈,同时维护好对应的区间 [le(i),i][le(i), i]
不要忘了 aia_i 的贡献(图中红色的一部分),还包括了从 dp(i1)(1)iaidp(i-1) \cdot (-1)^{i} \cdot a_i,即 ii 自身的一段
SSSS+conSS \leftarrow SS + con,这样就维护完成了

D

D. Yet Another Sorting Problem

置换

本例涉及到置换的性质,置换研究的是关于 nn 的排列
置换可以写成循环的结构

(1234535142)=(1,3)(2,5)(4)\begin{pmatrix} 1 & 2 & 3 & 4 & 5 \\ 3 & 5 & 1 & 4 & 2 \end{pmatrix} = (1, 3)(2, 5)(4)

比如 (1,4,3)(1, 4, 3) 这个循环表示 14,43,311 \to 4, 4 \to 3, 3 \to 1

长度为 22 的循环称为对换,比如 π=(i,j)\pi = (i, j)
置换 πSn\pi \in S_n 可以写成对换的乘积,比如
(1,2,3)=(1,3)(1,2)=(2,3)(1,3)(1, 2, 3) = (1, 3)(1, 2) = (2, 3)(1, 3)

置换的符号规定如下
π=τ1τ2τk\pi = \tau_1 \tau_2 \cdots \tau_k,其中 τi\tau_i 为一个对换,置换的符号为 ϵπ=(1)k\epsilon_{\pi} = (-1)^{k}
如果 ϵπ=1\epsilon_{\pi} = -1,为奇置换,如果 ϵπ=1\epsilon_{\pi} = 1 为偶置换

置换的循环分解算法
对于 nn 的排列,(1,2,3,4,,n)(1, 2, 3, 4, \cdots, n),最初的时候,循环分解为 (1)(2)(3)(n)(1)(2)(3)\cdots (n)
置换之后排列如下

(123n1n3n142)=(1,3)(2,n)(n1,4)\begin{pmatrix} 1 & 2 & 3 & \cdots & n-1 & n \\ 3 & n & 1 & \cdots & 4 & 2 \end{pmatrix} = (1, 3)(2, n)\cdots (n-1, 4)

实际上置换 (a,b)(a, b),可以用并查集将 a,ba, b 两个元素所在的集合合并
或者用图论来解决,aba\to b,连一条有向边 aba \to b

置换的奇偶性
首先循环分解中,循环的个数称为循环节 cyc\text{cyc}
(1,3)(2,5)(4)(1, 3)(2, 5)(4) 的循环节为 33

结论1
关于 nn 的排列 (1,2,,n)(1, 2, \cdots , n) 的奇偶性,与 ϵ=ncyc\epsilon = n - \text{cyc} 的奇偶性一致
特别地,如果排列有序,为初始状态 (1,2,,n)(1, 2, \cdots, n),它的循环节为 cyc=n\text{cyc} = n
它的 ϵ=0\epsilon = 0,为偶排列

结论2
排列 S=τSS' = \tau S,即在 SS 基础上,对换两个元素,那么 SS'SS 奇偶性相反

结论3
关于 nn 的排列 SnS_n 的奇偶性,还等于 SnS_n逆序对的个数

题解

有了以上结论之后,本例就简单了
首先,如果给出的序列是关于 nn 的排列,那么先算出其奇偶性 ϵ\epsilon
因为题中给出的置换是 (a,b,c)=(a,b)(b,c)(a, b, c) = (a, b)(b, c),它是一个偶置换
也就是说,如果答案为 yes\text{yes},那么给出的排列的奇偶性,和最终我们要的排列
(1,2,,n)(1, 2, \cdots, n) 的奇偶性要一致,(1,2,,n)(1, 2, \cdots, n) 是一个偶排列
所以当且仅当 ϵ\epsilon 为偶数的时候,才可行

如果不是关于 nn 的排列,由于 1ain1 \leqslant a_i \leqslant n,那就意味着 aa 中有两个相同元素
将其应用置换 π\pi 变为升序排列之后,如果为奇排列,那么可以将 πτπ\pi \leftarrow \tau \pi
即对于相同的元素,应用一次对换,这样奇排列总是可以转换成偶排列
所以非 nn 的排列,答案一定是 yes\text{yes}

E

Frequency Queries
题目大意
给定一棵树,每一个点 vv 有权值 a(v)a(v),给定问询 (v,l,k)(v, l, k),对于每个问询

  • vrootv \to root 路径上的所有数权值写出来,{a(v1),a(v2),,a(root)}\{a(v_1), a(v_2), \cdots, a(root)\} 构成一个序列
  • 计算这个序列中每个元素出现的次数 cnt[a(v)]cnt[a(v)],将出现次数 l\leqslant l 的元素移除
  • 删除冗余元素,并且将序列按出现次数升序排列,求出现次数第 kk 大的数,返回相应的 a(v)a(v)
    如果序列长度 <k< k,返回 1-1

考虑离线,将询问 (v,l,k)(v, l, k) 挂载到相应的点 vv

dfs(u)\textbf{dfs}(u) 的时候,可以维护出从 rooturoot\to u 的每一个点 vv权值 a(v)a(v) 出现次数
记为 cnt[a(v)]cnt[a(v)],具体来说,遍历到 uu 时,cnt[a(u)]++cnt[a(u)]++,回溯时 cnt[a(u)]cnt[a(u)]--

不难想到用树状数组,回答第 kk 大,此外我们还要用一个 setsetnum\text{num} 来支持插入,删除操作
遍历到某个点 uu,维护 num[cnt[a(u)]]={a(k1),a(k2),}\text{num}[cnt[a(u)]] = \{a(k_1), a(k_2), \cdots\}
表示出现次数为 cnt[a(u)]cnt[a(u)]权值有哪些

dfs\text{dfs}uu 的时候,具体要做几件事情

  • 首先 cnt[a(u)]cnt[a(u)] 肯定是要 +1+1 的,但是在增加之前,要在树状数组上执行 (cnt[a(u)],1)(cnt[a(u)], -1) 操作
    因为你 cnt[a(u)]+1cnt[a(u)] + 1 之后,原来出现次数为 cnt[a(u)]cnt[a(u)] 的数的个数就 1-1 了(前提是原来的 cnt[a(u)]>0cnt[a(u)] > 0
    与此同时记得在 num[cnt[a(u)]]\text{num}[cnt[a(u)]]erase\text{erase}a(u)a(u)
  • 然后执行 cnt[a(u)]+=1cnt[a(u)] += 1,表示出现次数为 cntcnt 的数的个数多了 11
    树状数组维护 (cnt[a(u)],1)(cnt[a(u)], 1),并且 num[cnt[a(u)]]\text{num}[cnt[a(u)]]insert\text{insert} a(u)a(u)
  • 接着就看看 uu 这个节点有没有挂载询问,有就可以回答问询了
  • 递归 uu 的子节点之后,回溯,并还原现场
    还原现场的时候先在树状数组执行 (cnt[a(u)],1)(cnt[a(u)], -1),并且在 num[cnt[a(u)]]\text{num}[cnt[a(u)]]erase\text{erase}a(u)a(u)
    然后 cnt[a(u)]=1cnt[a(u)] -= 1,如果 cnt[a(u)]>0cnt[a(u)] > 0,要在树状数组和 num\text{num} 中同步把 cnt[a(u)]cnt[a(u)] 给加回来

回答问询的过程,实际上是树状数组二分
删除 l\leqslant l 后的第 kk 个元素,实际上是问询,在不删除的条件下
l1\leqslant l-1 的元素有 k0k_0 个,在树状数组中二分查找第 k0+kk_0 + k 大的元素,得到这个元素的下标 xx
即可回答,第 k0+kk_0 + k 大的元素,它的出现次数为 xx,此时输出集合 num[x]num[x] 中任意一个元素即可
当然 k0+k>nk_0 + k > n 的话就无解

树状数组二分查找 x\geqslant x 的数逻辑如下
一开始令 p=0p = 0jjlgN0\text{lgN} \to 0 遍历每一层,此时树状数组的长条(p,p+2j](p, p+2^j]
检查树状数组对应的长条 C[p+2j]C[p+2^j],如果 p+2jnp+2^j \leqslant n 并且 C[p+2j]<xC[p+2^j] < x
那么接下来要在右子树中查找 x=C[p+2j]x -= C[p+2^j] 大的数,再令 pp+2jp \leftarrow p + 2^j
最后返回 pp 即可

一些杂题

魔数-第十八次CSP认证

这个问题一看没有什么思路,但区间修改不难想到线段树,相邻的两次操作如下
(UiUj)modP(U_i \cdot U_j) \bmod P,考虑打表,每出现一个新的数就新开一个状态编码,打表之后
idx=32idx = 32,也就是说,在模 PP 意义下
一个数 xx 乘以 {U0,U1,U4}\{U_0, U_1, \cdots U_4\} 中的任意一个乘以若干次,最终只会有 3232 种不同的结果,记为 Y[32]Y[32]
若干次 ×Ui\times U_i 的操作,等价于 x×Y[j],j[1,32]x \times Y[j], j \in [1, 32]

基于此,可以用类似有限状态自动机模型,来构造线段树区间修改,修改等价于状态转移
csp201912-5

  • 维护转移矩阵,g(i,j)=kg(i, j) = k 表示 Yi×UjYkY_i \times U_j \to Y_k,即编码ii 的数乘以 UjU_j 之后
    转移到编码为 kk 的数,其中 i[1,32],j[0,5)i \in [1, 32], j \in [0, 5),为了转移方便和统一,可以都用编码 mp\textbf{mp} 表示
    Yi×Uj=YkY_i \times U_j = Y_k,需要维护 g(i,mp[Uj])=kg(i, \textbf{mp}[U_j]) = k
  • 线段树的叶子结点,一开始的时候的 ff 值是 f(Al)=(lmodP)mod2019f(A_l) = (l \bmod P) \bmod 2019
    题中需要问询执行若干次 Al×Uj=AlA_l \times U_j = A_l' 之后的 lf(Al)\displaystyle\sum_{l}f(A_l'),根据前面的分析
    对于每个 llAlA_l' 只有 3232 种不同的取值,预处理为 Y[132]Y[1\cdots 32],所以考虑拆点
    将每个叶子结点拆成 3232 个不同的子节点,对于每个 AlA_l,可以预处理出 f(l,j),j=[132]f(l, j), j = [1\cdots 32]
    f(l,j)=((lYj)modP)mod2019f(l, j) = ((l \cdot Y_j) \bmod P) \bmod 2019,这里可以优化,不必要每个都执行乘法
    因为 l[1,n]l \in [1, n],可以递推,对每一个 Yj,j[1,32]Y_j, j \in [1, 32](lYj)modP=((l1)Yj+Yj)modP(l \cdot Y_j) \bmod P = ((l-1) \cdot Y_j + Y_j) \bmod P
    用一个 resres 记录一下 (lYj)modP(l \cdot Y_j) \bmod P,递推求解就可以
  • 特别地,f(l,0)f(l, 0) 表示对叶子结点不做任何修改,f(l,0)=Alf(l, 0) = A_l

有了叶子结点之后,考虑 pull\textbf{pull} 操作,线段树结点维护 ff 值,不妨设当前结点为 [l,r][l, r]
相应的值被修改为 [Al,Al+1,,Ar][A_l', A_{l+1}', \cdots, A_r'],结点 ff 值即维护 i=1rf(Ai)\displaystyle\sum_{i = 1}^{r} f(A_i')
pull\textbf{pull} 的时候只要把子区间对应的 f[j],j[0,32]f[j], j \in [0, 32] 相加就可以

csp201912-5-02

对于每一次操作,先考虑区间修改 upd(l,r,k)\textbf{upd}(l, r, k),即 ×Uk\times U_k 的操作
难点在于,区间 [l,r][l, r] 的子区间可能处于不同的状态 {sta1,sta2,sta3,}\{sta1, sta2, sta3, \cdots \},在 pull\textbf{pull} 的时候怎么合并?

当一个区间 ×Uk\times U_k 发生状态转移的时候,很可能子区间对应 {g(sta1,k),g(sta2,k),}\{g(sta1, k), g(sta2, k), \cdots\} 多个状态同步转移
所以转移的时候,3232 个状态要同时转移
p[1,32],g(ip,mp[Uk])=jp\forall p \in [1, 32], g(i_p, \textbf{mp}[U_k]) = j_p,有 f(i1,i2,,i32)f(j1,j2,,j32)\bm{f}(i_1, i_2, \cdots, i_{32}) \longrightarrow \bm{f}'(j_1, j_2, \cdots, j_{32})
pull\textbf{pull} 的时候子区间对应的 3232 位,挨个对应合并即可,这样假设区间乘以若干个 UkU_k 之后,等价于 ×Ys\times Y_s
此时如果子区间之前已经被修改过了,因为 pull\textbf{pull} 操作的存在,子区间和当前区间的 YsY_s 已经被更新成修改后的值
我们问询 f(s)f(g(s,k))\bm{f}'(s) \leftarrow \bm{f}(g(s, k)),它对应转移后的结果

另外,如何回答 i[l,r]i \in [l, r] 区间的 s=f(Ai)s = \sum f(A_i),那要注意到另外一个很重要的性质
因为本例对应的状态是有限的,仅有 3232 种,所以一定存在这样的转移
Al×(Up1Up2Upk)AlA_l \times (U_{p1} \cdot U_{p2} \cdots U_{pk}) \to A_l,即 Ys=Up1Up2Upk=1Y_s = U_{p1} \cdot U_{p2} \cdots U_{pk} = 1ss不动点
(否则的话状态应该是无限的)
也就是说 g(i,s)=ig(i, s) = i,初始化时候一定有 AlYs=AlA_l \cdot Y_s = A_lf(s)f(s) 这个状态表示区间映射到自身
这样只要打表找到 Ys=1Y_s = 1 的下标 ssf(s)f(s) 就表示区间和,经过若干次修改之后、
f(s)\bm{f}(s) 也会被更新为修改为 AA' 之后的区间 f(Ai)\sum \bm{f}(A_i')

那么这个问题大致就解决了,具体来说,执行 upd(l,r,k)\textbf{upd}(l, r, k) 操作的时候,找到对应的区间结点 pp
pp 的状态向量 ff 更新为 ff',具体来说,i[1,32],f(i)f(g(i,mp[Uk]))\forall i \in [1, 32], \bm{f}'(i) \longleftarrow \bm{f}(g(i, \textbf{mp}[U_k]))
同时打标记,如果 tag=0tag = 0,那么 tag=mp[Uk]tag = \textbf{mp}[U_k],否则,tagg(tag,mp[Uk])tag \leftarrow g(tag, \textbf{mp}[U_k])

延迟标记维护区间发生的一系列 ×(Up1Up2Upk)\times (U_{p1} \cdot U_{p2} \cdots U_{pk})累计状态转移,push\textbf{push} 的时候
记录当前区间的 tagtag,如果子区间的 tag=0tag' = 0,那么 tag=tagtag' = tag,否则 tag=g(tag,tag)tag' = g(tag', tag),然后清空当前区间的 tagtag
问询区间 sumsum 的时候,直接找到不动点 ss 并返回 f(s)\bm{f}(s)

特别地,有些情形下,这种转移没有不动点,需要手动构造
构造的方法也很简单,预处理以及初始化线段树的时候,令 l[1,n], f(l,0)=Al\forall l \in [1, n], \ f(l, 0) = A_l
线段树维护 ff 值,f(0)f(0) 表示区间没有任何转移的时候的 sumsum
构造转移矩阵,i[1,32], g(0,i)=i\forall i \in [1, 32], \ g(0, i) = i,这样 upd(l,r,k)\textbf{upd}(l, r, k) 的时候,就可以转移 ff
f(0)f(g(0,mp[Uk]))\bm{f}'(0) \longleftarrow \bm{f}(g(0, \textbf{mp}[U_k])),查询区间和的时候,返回修改后的 f(0)\bm{f}'(0) 即可
但是本例不能这样做,是因为 s=28s = 28 是不动点,即 2828 这个状态,对应 f(28)\bm{f}(28) 将区间映射为它自己
本例中的修改,可能存在其他状态转移到 s=28s = 28,我们的 f(28)\bm{f}(28) 要加上这部分贡献才能得到正确的结果
如果手动构造 f(0)f(0) 为不动点,原问题不存在从其他状态向 00 状态的转移,会漏掉一部分解