思路讲解
AC代码
1 |
和该问题一样,直接解决这个问题比较难,我们可以解决一些更简单的问题。
比如说就是让果汁恰好为X?
那么考虑我们要选 i1,i2,...ik 个,那么这些 i 要满足什么条件那?
这个问题其实也比较复杂,我们不妨只看 ...i1...i2... 这两个之间的情况。
我们发现,A[1]+⋯+A[i1]+⋯+A[i2−1]≥X,是必要条件。
而且 A[i1+1]+⋯+A[i2−1] 的加多了的部分还可以回馈给 A[i2]
那么我们就会形成一种感觉,就是说前面的部分其实不重要,越少越好,后面的部分要越多越好,因为还可以回馈给下一个。(抱歉,官解也没有证明,所以说只能这样讲了)
当然,我们需要将这种思维拓展到每一个相邻元素之间,那么我们发现,前面一个元素的后面下面一个元素的前面,因此我们要恰好,就是需要删的元素不能多(这个还是很容易发现的)。
于是我们不难写出程序。
https://www.codechef.com/viewsolution/1188472477
1 | // by Gospel_rock |
首先,做这道题目,需要有简单版的基础,简单版就是一个简单的贪心。以下是简单版的代码。
1 | FOR(i,2,N){ |
那复杂版就是多了一个在线修改+询问,一个询问就是把 A[p]:=x,然后 naive 的想法就是再跑一遍简单版的程序,当然,不难发现,时间复杂度肯定有问题。
经过简单尝试,我们发现这个贪心算法不是很好优化,因此我们可以想办法改进一下我们的这个贪心做法。
我们发现比较难优化,是因为我们想通过一种方法直接在 O(logn) 的时间复杂度内得到答案,这无疑有一定的难度。
那么,我们能不能在 O(logn) 的时间复杂度内判断一个答案可不可行那?
这肯定是比之前的问题简单多了,但也没有那么简单。
假设答案为 X,将原数组 A 表示为 0101... ,0 代表 <X,1 代表 ≥X。
我们不难发现(在题设保证 N 为奇数的情况下):
越后出现越重要。
10101 为相持阶段,得到的 B 是 11 类型,或维持原类型(即不能抵消 00)。
出现 11 即使得 B 变为 11 类型(抵消一个 00)。
同理,出现 00 即使得 B 变为 00 类型(抵消一个 11)。
那么通过上面发现的性质,那么我们其实就是要找到 11 & 00 的最靠右出现位置,这个可以使用二分答案+线段树二分 or 二分答案+二分+线段树解决。
具体来说是这样:
1 | auto check=[&](int mid){ |
代码:
https://www.codechef.com/viewsolution/1188151973
时间复杂度O(nlog2n)。
https://www.codechef.com/viewsolution/1188151973
1 | // by Gospel_rock |
(现在看起来,这个题目有一点难了,超出了我的理解范围)
说实话,看这道题目,真一点思路都没有,连图都没有,我怎么动态规划?
那么一个联通块作为一个大块是非常难做的,那有没有可能分成好几个小块那?可以的兄弟,可以的,把 1 删除以后,就形成了一个个的小块。这些小块的访问顺序就是
A[li],A[li+1]…,A[ri]
注意到,我们可以要求这些小块互不连通,那你说联通的情况怎么办呢?怎么统计那?

哈哈,这就用到了一个小巧思,把这个看作为联向 1 的回边,这样我们就能保证小块之间不联通(指的是删除点 1 后)。
想到这个了,我们就会发现,在保持现有联通块及其访问顺序的情况下,哪些点可以连回 1 那?这个其实也很简单,大于 A[li] 即可。
然后题目说,我们现在已经成功的把一个大问题分解为小问题了,那不妨用 dp 继续。
Observe that we’ve really just broken the original problem down into smaller problems of the same nature - which naturally suggests dynamic programming.
呃,其实我觉得和分治思想差不多,就是继续把 A[li] 当根。
然后我们不难发现,这个重点就在于怎么样求这个块的划分方法那?
其实这个递推式子是不难的,是符合直觉的,但是确实比较难绷。
因为整了两个数组,dp(dp[i][j] 表示从 i 到 j 的答案) 和 split(split[i][j] 表示从 i 到 j 有多少种划分方法) 实际上是一个东西(忽略联向根的回边),就是有一个差一,即:
split[i+1][j]=dp[i][j]
那么我们怎么递推得到 split 那?其实也简单,不难发现(忽略联向根的回边):
dp[L][R]=split[L+1][R]=x=L+1∑Rdp[L+1][x]⋅split[x+1][R]⋅[AL+1<Ax+1](为真时是1,反之为0)
这个式子看起来非常天外飞仙,其实很简单,就是把所有的分块都尝试一遍。
之所以分为两个,是为了避免差一错误。
你可能还会想,为什么一开始是 dp[L+1][x] 那?首先不用 split 是为了避免 split[L+2][L+1] 这种诡异的情况出现,然后 A[L+1] 是一定要为根的,因此用dp[L+1][x]。
那为什么后面一个代码是 split[x+1][R] ,而不是 dp[x+1][R] ?我也想了一会儿,不难发现 split[x+1][R] 可以保证 x+1 一定是根,但是不保证 x+2 一定是根,而采用 dp[x+1][R] 会导致不仅 x+1 一定是根,x+2 也一定是根。
好,那么现在问题就来到了我们怎么样实现了。题解给了一种比较好的实现方法,就是先枚举长度,再枚举起始点,因为不难发现长的区间是从短的区间转移过来的。
我感觉题解给的式子有点麻烦了,而且有点奇怪。
1 |
https://acm.hdu.edu.cn/contest/problem?cid=1172&pid=1005
这个题目的思路还是比较简单的,建虚拟点,
传送门
1 | while (!pq.empty()) { |
https://codeforces.com/contest/1941/submission/336750153
1 | // by Gospel_rock |