Codeforces Round #678 (Div. 2)/Codeforces1436 ABCDE
A. Reorder
可以推出
所以求个和但后看是不是和\(m\)相等就可以了。
B. Prime Square
直接构造。已知\(101\)是一个素数,然后令\(a_0 = 1, a_1 = 100, a_i = 0(2 \le i < n)\)。
令\(A_{j,i} = a_{(i + j) \% n}\),\(A\)的每一行每一列的和都为\(101\),且\(A\)中不含素数, 符合条件。
C. Binary Search
一次while
循环会得到一个\(middle\)。
如果\(middle < pos\),那么\(a_{middle}\)需要小于\(x\),记符合这一条件的\(middle\)的个数为\(L\);
如果\(middle = pos\), 那么\(a_{middle}\)需要等于\(x\);
如果\(middle > pos\), 那么\(a_{middle}\)需要大于\(x\),记符合这一条件的\(middle\)的个数为\(G\);
从\(x-1\)个数里选出\(L\)个,放在需要小于\(x\)的\(L\)个位置上,顺序随意,共有\(L! C_{x-1}^L种方法\)。
\(x\)必定在\(pos\)上,共有1种方法。
从\(n-x\)个数里选出\(R\)个,放在需要大于\(x\)的\(R\)个位置上,顺序随意,共有\(R!C_{n-x}^R种方法\)。
剩下\(n-L-R-1\)个数随便放,共有\((n-L-R-1)!\)种方法。
所以最终的答案就是\(L! C_{x-1}^L R!C_{n-x}^R (n-L-R-1)!\)。
D. Bandit in a City
解法1
二分最后的答案,然后每次用一次dfs去看这个答案是否符合要求。复杂度\(O(n \log n)\)。
正确性应该没问题,代码好像有点问题,WA好几发就换做法了。
解法2
解法1代码没写好,然后临时猜了个结论过了。
记\(sum_u\)为以\(u\)为根的子树和,\(sz_u\)为以\(u\)为根的叶子数。
E. Complicated Computations
解法1
观察:一个子数组的\(MEX\)为\(i\),当且仅当这个子数组包含\(1\)至\(i - 1\),但不包含\(i\)。
初始序列全为0,然后考虑从\(1\)到\(n+2\)逐次将所有值为\(i\)的元素插入序列。
枚举到\(i\)时,令\(a_0=a_{n+1} = i\),然后看在插入值为\(i\)的元素后,看是否存在\(MEX\)为\(i\)的子数组。这个时候,因为目前只加入了值为\(1\)至\(i\)的元素,所以只需要看两个相邻的\(i\)之间是否包含\(i - 1\)种非零数。
如果枚举到\(i\)时,发现不存在\(MEX\)为\(i\)的子数组,那么最终的答案就可以确定为\(i\)。
现在只需要解决动态区间种类数问题了,这个就比较看数据结构功力了,我整了个线段树套treap(想复杂了还整了可持久化,然后还WA了,其实普通的旋转treap或者非旋treap就够用了)。
动态区间种类数
具体一点的说,就是让线段树上的叶子节点保存上一个和自己相同元素的下标,然后线段树中的非叶子节点保存的就是所有它下面的叶子节点所存信息之和。为线段树中每一个节点建一棵treap来保存信息。这样子,每次更新就是线段树单点更新,也即对一条链上的treap做插入。
然后查询区间种类数的时候,就是查询区间\([l, r]\)内小于\(l-1\)的元素有多少个。这个其实就是一个线段树上的区间查询。
稍微的说明一下正确性吧,就是每个节点保存了上一个和自己相同元素的下标。所以:
- 如果区间\([l, r]\)内值为\(x\)的数只有一个,那么说明\(x\)记录的信息小于区间左端点,所以对结果的贡献为1。
- 如果区间\([l, r]\)内值为\(x\)的数不止一个,那么除了第一个\(x\),后面几个\(x\)记录的信息都会大于等于区间左端点,只有第一个\(x\)会有贡献,所以对结果的贡献为1。
这样一来,区间类同一种类的元素的贡献均为1,区间查询的结果即为区间内的种类数。
解法2
为方便讨论,对于所有值\(x\),在对\(x\)进行讨论时,认为\(a_0 = x\),\(a_{n+1} = x\)。
从左至右遍历序列,假设当前元素值为\(x\),位于\(cur\), 上一个值为\(x\)的元素位于\(last\)。
根据上述观察,对于\(x\),如果值为\(1\)-\(x-1\)的元素全都有在\((last, cur)\)这段区间中出现过,那么就存在MEX为\(x\)的子序列。
观察1: 因为是从左至右处理的序列,所以对于值为\(x\)的元素,如果它最后一次出现的位置大于\(cur\),那么就说明值为\(x\)的元素在\((last, cur)\)种出现过。
观察2: 对于值为\(1\)至\(x-1\)的元素,如果这些元素最后一次出现的位置的最小值大于\(last\),那么说明值为\(1\)-\(x-1\)的元素全都有在\((last, cur)\)这段区间中出现过。
用一个线段树\(T\)维护数据,\(T_x\)表示值为\(x\)的元素最后出现的位置。用一个数组存值为\(x\)上一次出现的位置为\(last_x\)。
这样一来,维护最后一次出现的位置就是线段树单点更新,查询值为\(1\)至\(x-1\)的元素最后一次出现的位置的最小值就是区间最小值查询。