牛客练习赛73 D.离别(尺取+离线线段树)

本文介绍了一种使用线段树处理离线查询的方法,通过预处理合法区间并结合尺取算法,有效地解决了特定类型的问题。适用于需要高效处理区间查询的场景。

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

传送门

考虑以每个 i i i为左端点,预处理右端点的合法区间为 [ l i , r i ] [l_i,r_i] [li,ri]

预处理的话只需要尺取就好了,因为处理 l i l_i li只需要处理到刚好有数等于 k k k

处理 r i r_i ri就是刚好有数等于 k + 1 k+1 k+1次,此时下标减去一即可

那么现在离线询问,按照左端点大到小排序

一开始 p o s = n pos=n pos=n

然后每次让 p o s pos pos左移动到当前询问左端点的位置

每移动到一个位置,就把那个点的合法右端点 [ l i , r i ] [l_i,r_i] [li,ri]加入线段树

然后查询的时候直接查询贡献就好了

查询询问 [ l , r ] [l,r] [l,r]的时候,在线段树上 [ l , r ] [l,r] [l,r]上的产生所有贡献的左端点都大于 l l l

所以不会多算

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mid (l+r>>1)
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson ls,l,mid
#define rson rs,mid+1,r
const int maxn = 2e6+10;
struct p{
	int l,r,pos;
	bool operator < (const p&tmp ) 	const{ return l>tmp.l; }
}a[maxn];
int L[maxn],R[maxn],w[maxn],laz[maxn],ans[maxn],n,m,k,c[maxn];
void pushdown(int rt,int l,int r)
{
	if( laz[rt]==0 )	return;
	w[ls] += laz[rt]*(mid-l+1); w[rs] += laz[rt]*(r-mid);
	laz[ls] += laz[rt]; laz[rs] += laz[rt];
	laz[rt] = 0;
}
void update(int rt,int l,int r,int L,int R,int val)
{
	if( l>=L&&r<=R ){ w[rt] += val*(r-l+1); laz[rt] += val; return; }
	if( l>R||r<L )	return;
	pushdown(rt,l,r);
	update(lson,L,R,val); update(rson,L,R,val);
	w[rt] = w[ls]+w[rs];
}
int ask(int rt,int l,int r,int L,int R)
{
	if( l>=L&&r<=R )	return w[rt];
	if( l>R||r<L )	return 0;
    pushdown(rt,l,r);
	return ask(lson,L,R)+ask(rson,L,R);
}
//尺取部分 
int sum = 0,vis[maxn];
void add(int index,int lim)
{
	if( vis[c[index]]==lim-1 )	sum++;
	vis[c[index]]++;
}
void del(int index,int lim)
{
	if( vis[c[index]]==lim )	sum--;
	vis[c[index]]--;
}
void ruler()
{
	for(int l=1,r=1;l<=n;l++)
	{
		while( r<=l )	add(r++,k);
		while( r<=n&&sum<1 )	add(r++,k);
		if( sum )	L[l] = r-1;	
		del(l,k);
	}
	for(int i=0;i<=n;i++)	vis[i] = 0; sum = 0;
	for(int l=1,r=1;l<=n;l++)
	{
		while( r<=l )	add(r++,k+1);
		while( r<=n&&sum<1 )	add(r++,k+1);
		if( sum )	R[l] = r-2;	
		else	R[l] = n;
		del(l,k+1);
	}
}
signed main()
{
	cin >> n >> m >> k;
	for(int i=1;i<=n;i++)	scanf("%lld",&c[i] );
	ruler();
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld",&a[i].l,&a[i].r);
		a[i].pos = i;
	}
	sort( a+1,a+1+m );
	int x = n;
	for(int i=1;i<=m;i++)
	{
		while( x>=a[i].l )	
		{
			if( L[x]!=0 )
				update(1,1,n,L[x],R[x],1); 
			x--; 
		}
		ans[a[i].pos] = ask(1,1,n,a[i].l,a[i].r); 
	}
	for(int i=1;i<=m;i++)	printf("%lld\n",ans[i] );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值