zoukankan      html  css  js  c++  java
  • 算法学习————猫树

    解决问题:

    猫树是解决无修改区间或树上询问的高效算法.

    对于常规问题,比如区间最值,区间最大子段和。我们常常能用线段树等数据结构达到,构造 O(n),询问 O(logn)的时间复杂度。

    对于这些做法,只有一点不好,询问复杂度 不够优秀,且对于一些特定问题,线段树的push_up合并也不好写。

    但对于区间最值这类的问题,我们可以类似 RMQ 那样,在一般的问题上,以预处理的时间和空间,换取快速的询问。

    算法流程

    我们首先考虑询问一个区间[ql,qr]。如果ql=qr,就可以直接得到答案。否则会不断在线段树上定位,而且会在几个区间的中点 mid 处被分开。

    考虑第一次被分开的位置,假设为p。那么原来的区间 [ql,qr] 就被分为[ql,mid],与[mid+1,qr] 。

    我们考虑对于每一个 mid 预处理他向前的后缀 [i,mid] 的答案(其中(lleq ileq mid)),以及他向后的前缀 [mid+1,j](其中(mid+1leq jleq r))。

    如果我们知道了p点所在的位置,我们可以直接利用之前预处理的[ql,mid]以及[mid+1,qr]的答案直接合并即可。

    不难发现预处理的复杂度是(O(nlogn))的(对于每一层每个数刚好被考虑一次 (O(n) imes O(logn)=O(nlogn)))

    然后怎么快速知道这个位置呢?

    不难发现这个p的位置,就是线段树上对应[l,l]和[r,r]节点的lca(最近公共祖先)所处的位置,我们可以考虑用st表预处理,然后可以直接查询p的位置,但这样显然太麻烦了。

    如果整棵线段树满足堆式存储(也就是对于点i,它的两个儿子分别为2i和2i+1),就有一个很好的性质。

    对于任意两个同深度的点,他们的lca是他们二进制下lcp(最长公共前缀)。

    我们把整棵树建成一个满二叉树[1,(2^k)],那么对于任意一个区间[i,i]都是满足他们的深度是最深且在同一层的。

    注意对于不同深度的点不一定满足这个情况!!这就是为什么我们为什么要建满的原因。

    然后对于两个数x,y的二进制下的lcp为 x >> Log2[x ^ y]。(这个很显然,丢掉第一个不相同的位后面的所有位就行了)

    这样我们就可以实现询问(O(1))

    例题:区间最长子段和

    因为每一层的区间都是不重叠的,所以我们可以把每一层写进一个数组里

    所以我们只需要预处理[i,mid]的最大前缀和与[mid+1,j]的最大前缀和,这个一边遍历一边取max。

    以及这两个区间的最大子段和。至于那个最大子段和,可以利用前缀和相减,保存一个前缀和最小值就行了

    因为无法注册SPOJ的账号,所以我也不知道写的对不对

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <algorithm>
    #define B cout<<"Breakpoint"<<endl;
    #define O(x) cout<<#x<<" "<<x<<endl;
    #define o(x) cout<<#x<<" "<<x<<" ";
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1e5+10;
    int val[maxn],maxlen,m;
    int ls(int x){return x << 1;}
    int rs(int x){return x << 1 | 1;}
    int Log2[maxn],n;
    struct CatTree{
    	int pos[maxn],Pre[maxn][30],Sum[maxn][30];
    	void build(int x,int l,int r,int dep){
    		if (l == r) {pos[l] = x;return;}	
    		int mid = (l+r >> 1),sum,minn;
    		Sum[dep][mid] = Pre[dep][mid] = sum = minn = val[mid];
    		minn = min(minn,0);
    		for (int i = mid-1;i >= l;i--){
    			Pre[dep][i] = max(Pre[dep][i+1],sum += val[i]);
    			Sum[dep][i] = max(Sum[dep][i+1],sum-minn);
    			minn = min(minn,sum);
    		}
    		Sum[dep][mid+1] = Pre[dep][mid+1] = sum = minn = val[mid+1];
    		minn = min(minn,0);
    		for (int i = mid+2;i <= r;i++){
    			Pre[dep][i] = max(Pre[dep][i-1],sum += val[i]);
    			Sum[dep][i] = max(Sum[dep][i-1],sum-minn);
    			minn = min(minn,sum);
    		}
    		build(ls(x),l,mid,dep+1),build(rs(x),mid+1,r,dep+1);
    	}
    	int query(int l,int r){
    		if (l == r) return val[l];
    		int dep = Log2[pos[l]]-Log2[pos[l]^pos[r]];
    		return max(max(Sum[dep][l],Sum[dep][r]),Pre[dep][l]+Pre[dep][r]);
    	} 
    }T;
    int main(){
    	n = read();
    	for (int i = 1;i <= n;i++) val[i] = read();
    	maxlen = 1;
    	while (maxlen < n) maxlen <<= 1;
    	T.build(1,1,maxlen,1);
    	for (int i = 2;i <= maxlen << 1;i++) Log2[i] = Log2[i >> 1]+1;
    	m = read();
    	for (int i = 1;i <= m;i++){
    		int l = read(),r = read();
    		printf ("%d
    ",T.query(l,r));
    	}
    	return 0;
    }
    
  • 相关阅读:
    mysql重置root密码
    node.js和JavaScript的关系
    转Git仓库分支(Branch)和标签(Tag)
    Spring MVC @CookieValue注解(5)
    Spring MVC @RequestParam(5)
    Spring REST(4)
    Spring MVC @PathVariable注解(3)
    Spring MVC @RequestMapping注解详解(2)
    Spring MVC入门示例(1)
    spring @Transactional注解参数详解(13)
  • 原文地址:https://www.cnblogs.com/little-uu/p/14743767.html
Copyright © 2011-2022 走看看