权值线段树+思维 牛客练习赛66 E题 骚区间

本文介绍了一种解决特定类型贡献题的方法,通过预处理和使用权值线段树,寻找符合条件的左边界和右边界,实现快速查找最大最小坐标,最后运用差分原理统计答案。

骚区间

这种贡献题可以发现,固定一个右边界 i ,然后在 1–i-1 找出所有符合条件的左边界 j ;

所以可以预处理出在 (1–i-1) 中大于 a[i] 的第一个元素位置p1,和第二个元素位置p2, 那么(p2+1,p1)就是可以成为 i 的左边界,同样可以处理右边界;这里可以运用权值线段树,查找最大最小坐标;

最后可以运用差分原理,统计答案;

代码:

#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=1000100;
const int M=2000100;
const LL mod=1e9+7;
int a[N],n,l1[N],r1[N],l2[N],r2[N]; 
vector<pa>ve[N];
struct Node{
	int l,r,mx,mi;
	LL w;
}tr[N*4];
void pp(int k){
	tr[k].mx=max(tr[ls].mx,tr[rs].mx);
	tr[k].mi=min(tr[ls].mi,tr[rs].mi);
	tr[k].w=tr[ls].w+tr[rs].w;
} 
void build(int l,int r,int k){
	tr[k].l=l,tr[k].r=r;
	if(l==r){
		tr[k].mx=0,tr[k].mi=2e9,tr[k].w=0;
		return;
	}
	int d=(l+r)>>1;
	build(l,d,ls);
	build(d+1,r,rs);
	pp(k);
}
void update_pos1(int pos,int w,int k){
	if(tr[k].l==tr[k].r){
		tr[k].mx=w;
		return;
	}
	int d=(tr[k].l+tr[k].r)>>1;
	if(pos<=d) update_pos1(pos,w,ls);
	else update_pos1(pos,w,rs);
	pp(k);
}
int query_pos1(int l,int r,int k){
	if(l>r) return 0;
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].mx;
	int d=(tr[k].l+tr[k].r)>>1;
	if(r<=d) return query_pos1(l,r,ls);
	if(l>d) return query_pos1(l,r,rs);
	return max(query_pos1(l,r,ls),query_pos1(l,r,rs));
}
void update_pos2(int pos,int w,int k){
	if(tr[k].l==tr[k].r){
		tr[k].mi=w;
		return;
	}
	int d=(tr[k].l+tr[k].r)>>1;
	if(pos<=d) update_pos2(pos,w,ls);
	else update_pos2(pos,w,rs);
	pp(k);
}
int query_pos2(int l,int r,int k){
	if(l>r) return 2e9;
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].mi;
	int d=(tr[k].l+tr[k].r)>>1;
	int ans=2e9;
	if(r<=d) return query_pos2(l,r,ls);
	if(l>d) return query_pos2(l,r,rs);
	return min(query_pos2(l,r,ls),query_pos2(l,r,rs));
}
void update(int pos,int w,int k){
	if(tr[k].l==tr[k].r){
		tr[k].w+=(LL)w;
		return;
	}
	int d=(tr[k].l+tr[k].r)>>1;
	if(pos<=d) update(pos,w,ls);
	else update(pos,w,rs);
	pp(k);
}
LL query(int l,int r,int k){
	if(l>r) return 0;
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].w;
	LL ans=0;
	int d=(tr[k].l+tr[k].r)>>1;
	if(l<=d) ans+=query(l,r,ls);
	if(r>d) ans+=query(l,r,rs);
	return ans;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	build(1,n,1);
	for(int i=1;i<=n;i++){
		int pos1=query_pos1(a[i]+1,n,1);//查找大于a[i]的元素最大下标 
		if(pos1){
			r1[i]=pos1;
			l1[i]=query_pos1(a[i]+1,a[pos1]-1,1)+1;
			l1[i]=max(l1[i],query_pos1(a[pos1]+1,n,1)+1);
		}
		update_pos1(a[i],i,1);
	}
	build(1,n,1);
	for(int i=n;i>=1;i--){
		int pos2=query_pos2(1,a[i]-1,1);//查找小于a[i]的元素最小下标 
		if(pos2!=2e9){
			l2[i]=pos2;
			r2[i]=query_pos2(1,a[pos2]-1,1)-1;
			r2[i]=min(r2[i],query_pos2(a[pos2]+1,a[i]-1,1)-1);
			if(r2[i]>n) r2[i]=n;
		}
		update_pos2(a[i],i,1);
	}
	for(int i=1;i<=n;i++){
		if(l2[i]&&r2[i]){
			ve[l2[i]].push_back(pa(i,1));
			ve[r2[i]+1].push_back(pa(i,-1));
		}
	}
	LL ans=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<ve[i].size();j++) update(ve[i][j].first,ve[i][j].second,1);
		if(l1[i]&&r1[i]) ans+=query(l1[i],r1[i],1);
	}
	printf("%lld\n",ans);
	return 0;
}
### 线段树维护区间的实现方法 线段树是一种特殊的线段树,其核心在于通过离散化的方式处理动态范围内的查询和更新操作。它通常用于解决涉及频率统计、第k大/小元素等问。以下是关于如何利用线段树来维护区间及其具体实现方式的详细介绍。 #### 定义与原理 线段树本质上是一个基于数组构建的数据结构,其中`tree[i]`表示某个范围内特定数出现的次数或某种属性总和[^1]。它的主要特点是将原始数据映射到一个连续的空间上,并在此基础上建立一棵二叉树形式的分治结构。这种设计使得我们可以高效完成单点修改或多点查询的任务。 对于维护区间而言,我们需要考虑以下几个方面: - **初始化**:首先对所有可能取进行排序并去重得到唯一列表;接着按照这些构造初始状态下的线段树。 - **更新操作**:当某位置上的数发生变化时,在对应的叶子结点处调整计数并向上传播直至根节点为止。 - **查询功能**:支持多种类型的询问,比如求解给定子区间内小于等于某一阈的数量或者寻找满足条件的第一位等。 #### Java 实现示例 下面给出一段简单的Java代码片段展示如何使用线段树来进行基本的操作: ```java public class WeightedSegmentTree { private int[] tree; public WeightedSegmentTree(int size){ this.tree = new int[size * 4]; } // 更新函数 (假设已知确切的位置pos) public void update(int pos, int delta, int l, int r, int idx){ if(l == r){ tree[idx] += delta; return ; } int mid = (l+r)/2; if(pos<=mid)update(pos,delta,l,mid,idx*2); else update(pos,delta,mid+1,r,idx*2+1); tree[idx]=tree[idx*2]+tree[idx*2+1]; } // 查询函数 [ql,qr] public int query(int ql,int qr,int l,int r,int idx){ if(ql>r || qr<l)return 0; if(ql<=l && r<=qr)return tree[idx]; int mid=(l+r)/2; return query(ql,qr,l,mid,idx*2)+query(ql,qr,mid+1,r,idx*2+1); } } ``` 上述代码展示了基础框架下如何执行单一维度上的增减变动以及累积量检索过程[^2]。 #### Python 实现示例 同样地,这里也提供了一个Python版本的例子供参考学习之用: ```python class SegmentTreeNode: def __init__(self, start, end): self.start = start self.end = end self.total = 0 self.left = None self.right = None def build_tree(nums, l, r): if l > r: return None node = SegmentTreeNode(l, r) if l != r: m = (l + r)//2 node.left = build_tree(nums, l, m) node.right = build_tree(nums, m+1, r) node.total = node.left.total + node.right.total else: node.total = nums[l] return node def modify(node, index, value): if not node or index < node.start or index > node.end: return if node.start == node.end and node.start == index: node.total = value return modify(node.left, index, value) modify(node.right, index, value) node.total = node.left.total + node.right.total def range_query(node, i, j): if not node or j < node.start or i > node.end: return 0 if i <= node.start and node.end <=j : return node.total return range_query(node.left,i,j) + range_query(node.right,i,j) # Example Usage nums = [1]*len(some_list_of_numbers_after_discretization) root_node = build_tree(nums, min_value_possible,max_value_possible) modify(root_node,some_index,new_count_or_summation) result = range_query(root_node,left_bound,right_bound) ``` 此部分说明了怎样创建一颗完整的树形结构并通过递归手段达到同样的目的——即快速定位目标区域进而实施必要的计算动作。 ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值