[CQOI2015]任务查询系统

本文讲解了如何使用区间和主席树数据结构解决区间查询问题,通过维护右括号和左括号的主席树,高效地计算经过点x且权值前k小的区间和,同时处理了自我继承和查询优化的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

区间包含点前k大问题

链接:[P3168 CQOI2015]任务查询系统 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:给定多个区间,每个区间有权值 q i q_{i} qi。多次询问经过点 x 的所有区间前 k 小权值的和。

题解:把区间当作两个括号(左括号,右括号),假设题目所求的是区间经过从 x 到结束的前 k 小权值和,那么其实维护右括号即可,按序列长度为时间轴建主席树,按右括号位置插入对应前缀,每次继承上一个时间点或当前时间点建树。查大于等于 x 的点只需要最后一棵树减去第 x-1 棵树即可。但是只经过 x 的区间的话,那么有一些d左括号大于 x 的区间也被算进来。那么再维护一棵左括号的主席树,查询 x 时就可以当作查右括号最后一棵主席树与右括号第 x-1 棵主席树的相减,同时减去左括号 x ( 1 ≤ x ≤ n ) x(1 \le x \le n) x(1xn) 到 n 的左括号主席树,这样可以消去那些大于 x 的区间的影响。

注意更新函数由于出现自己继承自己的情况,不能提前返回,先保留旧点信息。查询时注意查寻前 k 小时最后 l = r l=r l=r 时返回的值包含的个数可能大于当前所需的个数,所以要减去多的个数。同时可能 k 会大于当前所包含个数,取最大值 max ⁡ ( 0 , n u m − k ) \max(0,num-k) max(0,numk)

//#pragma GCC optimize("O3")
//#pragma GCC optimize("unroll-loops")
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>

using namespace std;
typedef long long ll;
const int maxn=1e5+5;

ll b0[maxn],n,m,x,a,b,c,k,ans=1,mx;
int ls[2][maxn<<5],rs[2][maxn<<5],sum[2][maxn<<5],rt[2][maxn],now[2];
ll t[2][maxn<<5];
struct node{
	int s,e,p;
}q[maxn];
vector<int>s[maxn],e[maxn];

void update(int o,int u,int&v,int l,int r,int pos)
{
	int p=++now[o];
	ls[o][p]=ls[o][u],rs[o][p]=rs[o][u];
	sum[o][p]=sum[o][u]+1,t[o][p]=t[o][u]+b0[pos];
	if(l<r)
	{
		int mid=l+r>>1;
		if(pos<=mid)update(o,ls[o][u],ls[o][p],l,mid,pos);
		else update(o,rs[o][u],rs[o][p],mid+1,r,pos);
	}
	v=p; return;
}

ll query(int u1,int v1,int u2,int v2,int l,int r,int k)
{
	int num=sum[1][v2]-sum[1][u2]-sum[0][v1]+sum[0][u1];
	if(l==r)return t[1][v2]-t[1][u2]-t[0][v1]+t[0][u1]-max(0,num-k)*b0[l];
	int mid=l+r>>1; num=sum[1][ls[1][v2]]-sum[1][ls[1][u2]]-sum[0][ls[0][v1]]+sum[0][ls[0][u1]];
	if(k<=num)return query(ls[0][u1],ls[0][v1],ls[1][u2],ls[1][v2],l,mid,k);
	else return t[1][ls[1][v2]]-t[1][ls[1][u2]]-t[0][ls[0][v1]]+t[0][ls[0][u1]]+query(rs[0][u1],rs[0][v1],rs[1][u2],rs[1][v2],mid+1,r,k-num);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>m>>n;
	for(int i=1;i<=m;i++)
	{
		cin>>q[i].s>>q[i].e>>q[i].p,b0[i]=q[i].p;
		s[q[i].s].push_back(i);
		e[q[i].e].push_back(i);  
	}
	sort(b0+1,b0+1+m),mx=unique(b0+1,b0+1+m)-b0-1;
	for(int i=1;i<=m;i++)
	{
		q[i].p=lower_bound(b0+1,b0+1+mx,q[i].p)-b0;
	}
	for(int i=1;i<=n;i++)
	{
		if(s[i].size())update(0,rt[0][i-1],rt[0][i],1,mx,q[s[i][0]].p);
		else rt[0][i]=rt[0][i-1];
		for(int j=1;j<s[i].size();j++)
		{
			update(0,rt[0][i],rt[0][i],1,mx,q[s[i][j]].p);
		}
		if(e[i].size())update(1,rt[1][i-1],rt[1][i],1,mx,q[e[i][0]].p);
		else rt[1][i]=rt[1][i-1];
		for(int j=1;j<e[i].size();j++)
		{
			update(1,rt[1][i],rt[1][i],1,mx,q[e[i][j]].p);
		}
	}
	for(int i=1;i<=n;i++)
	{
		cin>>x>>a>>b>>c;
		k=1+(a*ans+b)%c; 
		ans=query(rt[0][x],rt[0][n],rt[1][x-1],rt[1][n],1,mx,k);
		cout<<ans<<"\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值