前缀和与差分数组
多维的前缀和
S[i]=S[i−1]+a[i]
S[i][j]=S[i−1][j]+S[i][j−1]−S[i−1][j−1]+a[i][j]
S[i][j][k]=S[i−1][j][k]+S[i][j−1][k]+S[i][j][k−1]
−S[i−1][j−1][k]−S[i−1][j][k−1]−S[i][j−1][k−1]
+S[i−1][j−1][k−1]+a[i][j][k]
总之,有奇数个−1的是加,有偶数个−1的是减。
如果不想这么麻烦怎么办?可以先对行做一维前缀和,再对列做一维前缀和,即按定义并结合低维前缀和计算,是降维打击。
通过枚举左右端点,降到一维进行处理。
坐标同时+1可以方便处理。枚举右下端点,用二维前缀和计算子矩阵和。
注意二分的循环条件为r−l>1,因为当r=l+1时无论mid偏向哪一边都会导致无限循环。
dp。f[i][j]表示(1,1)→(i,j)区域未开采,其余部分已开采的最大矿数和。也可以f[i][j]表示(1,1)→(i,j)区域已开采,其余部分未开采的最大矿数和。
跳棋
- 反向dp。倒过来跳,[li,ri]中的点可以跳到i。$$f[i]=\sum^{r}_{j=l}f[j]$$
用“后缀和”处理。
- 正向dp。对于每一个i,给f[j](j∈[li,ri])加上f[i]。
用差分数组(边弄边查)达到O(n)。
先二分求出最大值的最小值w,可以O(nlog∑l)也可以O(lognlog∑l)。
此处笔记:二分时使用半开区间,如[l,r),(l,r]是很方便的,但是注意循环判断条件要取r−l>1。
再进行dp。f[i][j]表示考虑到第i根木棍,已经切了j次的方案数。f[i][j]从f[t][j−1]转移而来,其中t+1到i的和不超过w。
如何维护t呢?可以二分(本蒟蒻的想法),但是这样就会额外产生对数因子。大佬用的方法,就是由于i是顺序枚举的,所以t的左界限也是单调向右移动的。只需要弄一个指针,在i向右移动的时候把t相应向右移动即可。可理解为“双指针法”。
类似的例子有dp中的“决策单调性”(石子合并),也是根据上次的决策确定本次的决策范围。
这道题对于空间的限制很紧,只能使用short。另外注意如果要计算a−b(modp),一定要(a+p−b)%p,否则变成负数了就没得救了。
dp,f[x][y]表示到达点(x,y)所需的最小点击次数,若f[x][y]=+∞表示这个点不可达。
这里必须逆向转移才能优化。
f[x][y]可以从f[x−1][y+Yx−1](掉了Yx−1)或者f[x−1][y+kYx−1](k∈N+)(点了k次)转移而来。
先考虑点了的情形。
可以发现y+kYx−1对于Yx−1是同余的,并且重复地做了求min操作。可以对同余的点做一个前缀/后缀min,即记录g[x][y]=min(g[x][t])(t≡y(modYx),t≤y)思路类似多重背包的单调队列优化。
也可以按mod Yx−1分组计算,用f[x][y]=min(f[x−1][y−Yx−1],f[x][y−Yx−1]+1),因为到达(x,y)所需的点击次数恰好比到达(x,y−Yx−1)的次数多1。
时间复杂度O(nm)。
这题在实现上有相当的难度,尤其要注意碰到顶的情形。额外判断是否能碰到顶时,要注意代码实现。
1 2 3 4 5 6 7 8 9 10 11 12
| if (ubound==m){ for (int j=1;j<=m;j++){ int tans=(m-j)/x[i-1]+(((m-j)%x[i-1])?1:0); f[i][m]=min(f[i][m],f[i-1][j]+max(1,tans)); } }
if (ubound==m) for (int j=max(m-x[i-1],l[i-1]+1);j<=m;j++) f[i][m]=min(f[i][m],f[i-1][j]+1);
|
上面错误的写法错在,一个位置可能重复点击多次才碰到顶。
45分:暴力。
{90,100}分:线段树,区间最小值。普通线段树常数太大,会TLE一个点;也许zkw线段树可以AC。
100分:注意到前k个订单能否满足是单调的,因此可以考虑二分可以满足的订单数。对于一个k,首先O(m)用差分标记区间加,再O(n)检查是否满足要求。总复杂度O((n+m)logm)。
想到满分算法的思路应该是先考虑枚举能满足的订单数,使用差分数组进行维护,再发现单调性,将m降为logm。差分数组处理每一个订单是O(1)的,然而线段树是O(logn)的,故差分数组的解法具有优越性。
如果这题一开始就想到线段树,可能对思维定势造成干扰,从而无法得到正解。
这道题HDM讲过呢,可是我印象不是很深刻了……
证明的要点:区间只会有覆盖而不会有交叉。
记住要去重。
显然很难,所以考虑拿40分。
前20分是送你的。
第5个点暴力是否可过?
对于退化成链的15分,考虑能被第j个点看到的玩家,他一定在t=Wj时位于第j个点,因此他的起点Si=j−Wj且终点Ti≥j,或Si=j+Wj,Ti≤j。
我的想法是把玩家按(Si,Ti)为第一、第二关键字排序,把观察员拆成(j+Wj),(j−Wj)两个查询。用两个指针,单调地处理查询。对每一个查询,寻找满足条件的玩家,记录到ans[j]中。
老师的想法是,先处理出以x为起点的点的数目cnt[x],按顺序枚举观察员,当一个玩家的终点超出观察员的观察范围时把这个玩家从cnt[x]中减掉。
群里大佬概括指出,Wj相当于起点与观察点的距离。也许根据这个思路,就能解决Si=1和Ti=1的数据了呢(这样好像就80分了……)
栈
HDM讲过,是POJ2559。
首先最终的矩形的高度肯定等于某一个柱的高度,因此先枚举矩形的高度。
设f[i]表示i左边高度不小于h[i]的连续柱子段的长度,那么如果h[i]≥h[i−1],那么i−1柱所满足的连续柱子段,一定也可以用于i柱。而如果h[i]>h[i−1]就意味着f[i]=0。
如果有h[i]≥h[i−1],那么我们在计算f[i]的时候就可以直接利用f[i−1]。如果有连续的几个不大于h[i]的顶点,我们可以把它们的信息合并,只使用最右边的一个。
体现在程序实现上,我们维护一个栈s,s中元素的h值是单调递增的。我们枚举到i时,就删除栈中h[k]≥h[i]的元素k,并把f[k]+1累加入f[i]中,直到栈顶元素h[k]<h[i]或栈空。
再计算g[i]表示i右边高度不小于h[i]的连续柱子段的长度,则答案即为max((f[i]+g[i])×h[i])。
降维打击例题:玉蟾宫
枚举矩形底端所在的行,转化为上一题。
维护单调数组g[i],表示长度为i的最长不升子序列的最后一个元素的最大值(为了给后面的元素留下最大的余地)。由于序列越长,最后一个元素一般就越小,因此g的值是单调不升的。
对于一个i,在g中二分寻找不小于a[i]的最后一个(最长的)g[j],则以i结尾的最长不升子序列的最大长度就是j+1。接着更新g数组,令g[j+1]=max(g[j+1],a[i])=a[i],因为根据二分的过程,a[i]一定大于g[j+1]。
最长上升子序列同理。
线段树可以水过去,但是码量稍大(雾?)
“如果一个人比你小,还比你强,你就比不过他了。”
“如果一个元素在你后面,比你大,你就被他掩盖了。”
维护栈s,栈中元素单调递减,越靠前的元素就越老。加入一个元素时,把栈中小于它的元素全部删除,请退役(可以用二分辅助)。查询后L个元素的最大值时~~(国际初中生信息学奥林匹克)~~,在栈中二分寻找最靠前(最大)的一个满足位置不小于L的元素。
维护单调不增栈,越靠前的元素就越大。加入一个元素时,先在栈中二分找出比它大的最后一个(最小的)元素,,再把栈中小于它的元素全部删除,可以用二分辅助。可以把相同的数据压缩进一个cnt里。
先考虑一维的情形,如11001101,则11这一段有3个,1这一段有1个。归纳得出,如果某一段有n个连续的1,这一段的矩形数就是∑i=1ni。
对于二维的情形,考虑降维打击。枚举矩形的左端点和右端点,把同一行左右端点内的所有格子的按位与运算的结果作为一维时矩形的值。
真的做一遍按位与吗?那样就是O(n4)的呢,还不如直接枚举矩形的左上右下端点。考虑线段树求区间最小值 考虑一个前缀和数组,如果这一段的长度是l,那么这一段里面全都是1当且仅当这一段的区间和也为l。
总复杂度O(n3)。