线段树写法不管,比较灵活。这里主要讨论DP实现。
其实单纯说RMQ解决的是区间最值查询是不准确的,只要满足一个区间的信息可以从它的覆盖区间获得(即[L,R]<=[L,r],[l,R] (l<=r) ,允许两个子区间重合)即可使用。重合不影响最值判断,所以最值查询是可以用RMQ的,其次如同区间gcd,重合区间也是不影响求值的。一样可以用RMQ。
下面用RMQ的实现来解释上述结论:
dp[i][j]表示以i起始,长度为2^j的线段信息。它可以划分为dp[i][j-1],dp[i+2^(j-1)][j-1],所以状态转移就出来,dp[i][j]=某种计算(dp[i][j-1],dp[i+2^(j-1)][j-1];
查询的话,如果像线段树那样用递归分解区间求解,复杂度log级,因为是严格分离区间,所以不存在之前说的重合影响(所以线段树适用范围大啊)。而DP实现要求在O(1)时间给出答案,所以我们可以把查询区间分解为两个尽可能大的子区间(允许覆盖),这有个很简单的写法,对于区间L,R的查询,求一下k=log2(R-L+1),两个子区间为dp[L][k],dp[R-2^k+1][k];
以区间gcd为例:
LL lg2(LL p)//计算log2(n) { return (LL)(log(p) / log(2)); } const LL len = 100005;//数据长度 const LL bitlen = 25;//需要的位数 LL n; LL dp[len][bitlen];//dp数组 LL bit[bitlen];//预处理2^n void initRMQ()//初始化 { for (LL j = 1; bit[j] < n; j ++) for (LL i = 0; i+bit[j] <= n; i++) dp[i][j] = gcd(dp[i][j - 1], dp[i + bit[j - 1]][j - 1]); } LL query(LL l, LL r) { LL mig = lg2(r - l + 1.0); return gcd(dp[l][mig], dp[r - bit[mig] + 1][mig]); }
对于需要增删改的操作,显然是线段树比较合适,对于有大量查询操作的题目,RMQ可以有效降低时间(最近遇到的这类题目对时间空间要求都很高,写代码写丑一点就挂了。提醒自己多注意)。
时间: 2024-09-07 08:35:09