56E - Domino Principle
我们发现,倒下的多米诺骨牌一定是一个区间,否则如果中间空了一段,前面就一定不能影响到后面。所以可以设 \(r_i\) 表示第 \(i\) 块牌倒下,倒下的最右的牌。然后每块牌影响的范围就是 \([i,r_i]\)。我们计算它能直接使得倒下的牌是哪些区间,\(r_i\) 就是这个区间中的区间 \(r_i\) 最大值。可以用二分查找+线段树解决。
但是我们也可以用单调栈解决,因为单调栈栈顶的数只要能被够到,就一定会被更新到,那么新的值就一定大于等于它。所以可以暴力把栈顶所有被覆盖的数弹出更新答案,复杂度 \(O(n)\)。
570D - Tree Requests
首先,我们套路化的先 \(dfs\),再按照 \(dfn\) 序 \(bfs\),使得任意子树内同一深度的所有点构成 \(bfs\) 序的一个区间。然后考虑什么情况下能打乱构成回文串。也就是所有字符中最多只有一个出现次数是奇数,其他都是偶数。第一个思路就是离线然后暴力莫队,复杂度 \(O(n\sqrt n)\) 在字符集够大的时候,这可能是最优的办法,在这个题也能跑过去。
但是我们发现字符集非常小,可以直接状压,用一个 int
存储当前前缀各个字符出现的次数是奇数还是偶数。用区间首位异或一下就能得到区间的答案。然后检查是否满足要求。复杂度 \(O(n\log n)\),瓶颈在于找到当前询问的子树深度点对应的 \(bfs\) 序区间。
52C - Circular RMQ
我们发现,操作一个环上区间 \([lg,rg](lg>rg)\) 其实就是区间 \([0,rg]\cup[lg,n-1]\),所以就可以直接转化成区间加区间最小值问题。然后就直接解决了。复杂度 \(O(n\log n)\)。
257E - Greedy Elevator
发现可以直接模拟。我们每次记录下一个有事件发生(出现等电梯的人或有人上下电梯)的时间。在当前时间到下一个事件之间,电梯的运动状态不变。然后每到达一个新的时间,加入新的事件。事件分为两类,需要电梯向上触发的和需要电梯向下触发的。我们可以分别存储。发现一个事件和电梯的相对位置一旦改变,意味着它被触发,所以一个事件一定一直在下方或者一直在上方。
这样我们就可以暴力模拟了,对于每个楼层记录到这个楼层会触发的事件,记录上方事件和下方事件。每次解决当前楼层事件之后,重新决定运动状态,计算以当前运动状态到达下一个存在待触发事件楼层需要的时间,和下一个人出现的时间取 \(\min\),即可模拟。每个下电梯事件触发的时候记录答案即可。
617E - XOR and Favourite Number
转化成前缀和,考虑莫队,其实我们只是在询问 \([l-1,r]\) 区间内,有多少个 \(l-1\le i<j\le r\),满足 \(s_j\oplus s_{i}=k\),发现虽然是有序点对,但是贡献可逆,所以只要保证只会被贡献一次即可。那么就用莫队来处理,每次记录当前区间内所有的 \(s_i\),然后加入一个数 \(s_j\) 的时候,先计算它和区间内所有数的贡献,也就是 \(k\oplus s_j\),然后加入。删除的时候,要先删除,然后减去它和剩余区间内数的贡献,防止计算自己和自己的贡献。
404E - Maze 1D
首先发现,石头最多放一个,否则如果两个在同一边,有一个无效,两个在两边,整个区间被覆盖,最后不可能达到目的。然后我们强制最后一个是 L
,否则就全体翻转。
然后,第一个思路是,放石头的实质是找到序列的第一个最大值的位置,将其开始的整个后缀都减一。因为后面即使有相同的值,再一下也被减掉了,所以序列的最大值是单调减小的。然后我们只需要看前 \(n-1\) 次操作中到达的最低点是否达到最后一次操作的位置,线段树上维护区间减和区间第一个最大值及其位置即可。
但是我们可以发现,放石头的位置是单调的。也就是,如果放在 \(x(x>1)\) 可以,放在 \(x-1\) 也一定可以。因为实际上往左移会抵消一些负贡献的操作,但是对正贡献的操作没有任何影响。那么就可以二分然后暴力模拟检查,复杂度也是 \(O(n\log n)\)。
369E - Valera and Queries
考虑计算不覆盖任何 \(p_i\) 的区间数量。也就是对于每个 \((p_{i-1},p_i)\),考虑左右端点都在区间内的区间个数。特别的,我们令 \(p_0=0,p_{cnt+1}=10^6+1\)。
这就可以考虑树套树,对于每个节点都开一个平衡树储存所有左端点落在区间内的区间的右端点,然后区间查找 \([l,r]\),在每个查询区间都在平衡树上查询不超过 \(r\) 的个数有多少个。然后发现操作是静态的,可以用 vector
排序后二分替代。复杂度 \(O(n\log ^2 n)\)。
282E - Sausage Maximation
考虑前缀和 \(pre_i\) 和后缀和 \(suf_i\),我们其实就是要 \(\max_{i<j} pre_i\oplus suf_j\)。考虑倒序在 \(01-trie\) 上加入 \(suf_i\),每次先对于 \(pre_i\) 在 \(01-trie\) 上查询异或最大值,然后将 \(suf_i\) 加入。注意不要漏掉 \(pre_0=0\) 和 \(suf_{n+1}=0\)。
279D - The Minimum Number of Variables
状压 \(dp\),设 \(dp_{i,msk}\) 表示当前 \(dp\) 到位置 \(i\),\(msk\) 中的所有 \(j\) 的 \(a_j\) 还在 \(b\) 中的最少 \(b\) 个数。
发现因为数字互不相同,所以每个数,最多有 \(i/2\) 个用 \(<i\) 的数组合出自己的方法。所以如果 \(msk\) 中检索不到任何拼出 \(i\) 的方案就排除。然后看新的值放在那里,如果替换掉原来的,容器数量不变,枚举替换掉哪一个。否则容器总数加一。
然后考虑时间,看起来是 \(O(n^22^n)\) 的,但是实际上,首先我们发现 \(dp_{i,msk}\) 中只会有最高位是 \(i-1\) 的 \(msk\),所以合法的状态一共只有 \(2^{n-1}\) 种,最后的状态完全不必计算。然后复杂度其实就是 \(\sum_{i\le n} i2^{i-1}\),\(n=23\) 大概是 \(5\cdot 10^7\) 这个级别,还是很好过的。
165E - Compatible Numbers
发现能和 \(x\) 与起来为 \(0\) 的数,一定是 \((2^{22}-1)\oplus x\) 的子集。也就是 \(y\) 所有有 \(1\) 的位,\(x\) 这边都是 \(0\),它的逆都是 \(1\)。
那么设 \(dp_{msk}\) 表示任意一个 \(x\subseteq msk\) 出现在 \(a\) 中,没有就是 \(-1\)。那么 \(dp_{a_i}=a_i\),\(dp_{msk}=max_{x\subseteq msk} dp_x\)。这样的复杂度是 \(3^{\log a_i}\),过不去。但是我们发现,如果 \(x\subseteq y\subseteq msk\),那么 \(x\) 和 \(y\) 对 \(msk\) 的贡献是一样的。我们就可以只用 \(y\) 贡献 \(msk\) 而不必使用 \(x\)。那么,我们就可以只用 \(msk\) 的所有最大真子集贡献,也就是枚举去掉哪一个。这样的算法是 \(O(a_i\log a_i)\) 的。可以通过。
431E - Chemistry Experiment
首先,我们需要把所有的 \(h_i\) 和 \(x_i\) 离散化下来,不然就要用平衡树,这是不合算的。然后用线段树维护当前区间的杯子个数和和水量和。第一种思路是先二分查找 \(x\),然后查找当前的 \(x\) 是否可行。那么我们就是把所有 \(h_i<x\) 的拿出来,假如个数是 \(m\),总水量是 \(s\),那么就要满足 \(xm-s\ge v\)。考虑用线段树维护 \(m\) 和 \(s\) 的前缀和。因为我们需要二分查找第一个 \(\ge x\) 的位置,所以树状数组是不太可行的。复杂度是 \(O(n\log w\log n)\)。
然后考虑,二分答案既然可以,我们为什么不在线段树上二分呢?线段树的每个区间 \([l,r]\) 实际上代表着 \((b_l,b_{r+1}]\)(\(b\) 是离散化数组),所以每次我们记录已经被加入答案的 \(m\) 和 \(s\),将左儿子的 \(m\) 和 \(s\) 加入,计算区间剖分点(\(b_{mid+1}\))是否可行。如果可行就往左儿子,否则就往右儿子。然后在叶子节点的时候,我们发现选择的 \(m\) 和 \(s\) 已经确定了,就不需要继续二分了,直接取 \(\max\{b_l,(v+s)/m\}\) 即可。复杂度 \(O(n\log n)\),还省去了实数二分的麻烦。
965E - Short Code
考虑建出 \(trie\) 树,改变问题,在 \(trie\) 树上有一些节点有东西,我们可以将其上移,然后要求最终东西不重合,最小化深度总和。我们考虑每个子树而言,我们把它的最优安排策略放下,然后考虑合并,如果在一个节点本来没有东西,那么就可以在它的子树中找一个删掉,放在这里。那么我们贪心的选深度最大的。也就是我们需要维护每个子树的动态最大值。可以用启发式合并维护,复杂度 \(O(n\log n)\)。
514E - Darth Vader and Tree
发现 \(d\) 的值域是突破点,其实也就是我们每次选择一种长度,选择它的贡献就是它在原序列中出现的次数。所以转移值域是 \(100\)。
然后考虑 \(dp\),\(dp_i\) 表示深度为 \(i\) 的节点个数,然后有转移式 \(dp_i=\sum_{1\le j\le 100}c_jdp_{i-j}\),发现是线性递推,可以用矩阵加速计算数列。但是发现我们需要计算的是 \(\sum_{i\le x}dp_i\) 而不是 \(dp_x\)。考虑 \(s_i=\sum_{j\le i}dp_j\),那么 \(s_i=s_{i-1}+dp_i=s_{i-1}+\sum_{1\le j\le 100}c_jdp_{i-j}\)。
我们发现 \(dp_{i-j}\) 可以用 \(s_{i-j}-s_{i-j-1}\) 得到,所以 \(s_i=s_{i-1}+dp_i=s_{i-1}+\sum_{1\le j\le 100}c_j(s_{i-j}-s_{i-j-1})\)。于是 \(s_i=s_{i-1}+\sum(c_j-c_{j+1})s_{i-j}\)。这样就是一个新的线性递推,递推式不超过 \(101\) 项,可以简单的使用矩阵快速幂加速计算,复杂度 \(O(d^3\log x)\)。
标签:le,08,我们,msk,区间,杂题,复杂度,dp From: https://www.cnblogs.com/jucason-xu/p/17468595.html