SWUST 2885.连续段(主席树)

题目

给你一个长度为n的排列,每次询问由l,r构成,你需要回答区间[l,r]形成的连续段数,连续段由是由多个相邻的数构成。

例如1,2,3,4为一个连续段,1,3是两个连续段.

对于一个长度为5的排列为:1,3,5,2,4。询问区间为[2,4]时,连续段数量为2,即2-3与5。

本题强制再线,真实的询问区间需要异或上一次答案,

l=l⨁lastans,r=r⨁lastans,其中⨁表示异或运算,最开始lastans=0。

询问区间不合法,即l<1或者l>r或者r>n时,忽略本次查询

思路来源

my学长

题解

考虑一个区间[l,r]里的数,初始认为每个都是独立的一段,即r-l+1段,

单独考虑ai和ai+1,对于段数减少带来的影响,

考虑合并,对于ai来说,

如果ai+1也位于[l,r]内,则ai和ai+1是一段,可合并,导致最终少一段;

如果ai+1不在[l,r]内,则ai单独自己是一段

所以对于i来说,i位置存pos[ai+1],

查询[l,r]内pos值位于[l,r]内的数的个数,再用总段数减去即可,

由于都是[1,n]范围,则用主席树

代码1

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,l,r,a[N],pos[N];
int root[N],ls[50*N],rs[50*N],sum[50*N],c;
int lasans;
void upd(int l,int r,int &cur,int las,int pos){
	cur=++c;
        ls[cur]=ls[las];rs[cur]=rs[las];
	sum[cur]=sum[las]+1;
	if(l==r)return;
	int mid=(l+r)/2;
	if(pos<=mid)upd(l,mid,ls[cur],ls[las],pos);
	else upd(mid+1,r,rs[cur],rs[las],pos);
}
int ask(int l,int r,int cur,int las,int ql,int qr){
	if(ql<=l&&r<=qr)return sum[cur]-sum[las];
	int ans=0;
	int mid=(l+r)/2;
	if(ql<=mid)ans+=ask(l,mid,ls[cur],ls[las],ql,qr);
	if(qr>mid)ans+=ask(mid+1,r,rs[cur],rs[las],ql,qr);
	return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        pos[a[i]]=i;
    }
    for(int i=1;i<=n;++i){
        //printf("%d: pos:%d\n",i,pos[a[i]+1]);
        upd(0,n,root[i],root[i-1],pos[a[i]+1]);
    }
    scanf("%d",&m);
    while(m--){
        scanf("%d%d",&l,&r);
        l^=lasans;r^=lasans;
        if(l<1 || l>r || r>n)continue;
        printf("%d\n",lasans=((r-l+1)-ask(0,n,root[r],root[l-1],l,r)));
    }
    return 0;
}

代码2

my学长的代码,

感觉这个主席树写法,更像线段树一点

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int N=110000;
const int M=4100000;
const LL mod=1e9+7;

int n,m;
int a[N],lastans;
int rk[N],root[N];
int ch[M][2],pos,sum[M];
int copy(int x)
{
	pos++;
	ch[pos][0]=ch[x][0];
	ch[pos][1]=ch[x][1];
	sum[pos]=sum[x];
	return pos;
}
void add(int k,int l,int r,int x)
{
	if (l==r) {sum[k]++;return;}
	int mid=(l+r)/2;
	if (x<=mid) add(ch[k][0]=copy(ch[k][0]),l,mid,x);
	else add(ch[k][1]=copy(ch[k][1]),mid+1,r,x);
	sum[k]=sum[ch[k][0]]+sum[ch[k][1]];
}
int ask(int k,int l,int r,int a,int b)
{
	if (!k) return 0;
	if (a==l&&b==r) return sum[k];
	int mid=(l+r)/2;
	if (b<=mid) return ask(ch[k][0],l,mid,a,b);
	else if (a>mid) return ask(ch[k][1],mid+1,r,a,b);
	else return ask(ch[k][0],l,mid,a,mid)+ask(ch[k][1],mid+1,r,mid+1,b);
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		rk[a[i]]=i;
	}
	root[0]=pos=1;
	for (int i=1;i<=n;i++)
	{
		root[i]=copy(root[i-1]);
		if (a[i]<n) add(root[i],1,n,rk[a[i]+1]);
	}
	scanf("%d",&m);
	while (m--)
	{
		int l,r;
		scanf("%d %d",&l,&r);
		l^=lastans,r^=lastans;
		if (l>r||l<1||r>n) continue;
		int ans=r-l+1;
		ans-=ask(root[r],1,n,l,r)-ask(root[l-1],1,n,l,r);
		printf("%d\n",ans);
		lastans=ans;
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值