【洛谷 2021.11.1团队模拟赛】可重集【线段树】

102 篇文章 1 订阅
17 篇文章 0 订阅

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
》》example2


解题思路

首先,假设所有操作修改的数字都是一样的,那么无论 l,rl,r 怎么取值,每一个删除操作,删除的都是固定的加入操作。

考虑预处理出每个删除操作对应的加入操作的时间点。(我处理的很麻烦,排序+栈)

线段树:单点修改区间查询
考虑把查询的区间按右端点排序,每次按时间线向后更新线段树,若当前时间点是加入操作就在把当前时间点修改为x,如果是删除,若曾经加入过这个点,就在线段数上把这个删除操作对应的加入操作的值修改为1,这样在乘的时候就相当于乘了1,也就是删掉了这个数。

查询的时候把一段区间中所有数乘起来就行了


代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int n,m,l,r,v[300010];
ll p,kj[300010];

struct c{
	int opt,id,t,l,r;
	ll x;
}a[300010],s[300010],aa[300010];

struct cc{
	int l,r;
	ll x;
}tree[20001000];

bool cmp(c l,c r)
{
	if(l.x==r.x)return l.t<r.t; 
	return l.x<r.x;
}

bool cmp1(c l,c r)
{
	return l.r<r.r;
}


stack<int>s1;

void build(int k,int l,int r){
	tree[k].l=l,tree[k].r=r;
	tree[k].x=1;
	int mid=(l+r)/2;
	if(l==r)
	{
		tree[k].x=1;
		return;
	}
	build(k*2,l,mid);
	build(k*2+1,mid+1,r);
}

void change(int k,int x,ll v){
	int l=tree[k].l,r=tree[k].r;
	int mid=(l+r)/2;
	if(l==r)
	{
		tree[k].x=v;
		return;
	}
	if(x<=mid)
		change(k*2,x,v);
	if(x>mid)
		change(k*2+1,x,v);
	tree[k].x=tree[k*2].x*tree[k*2+1].x%p;
}

ll find(int k,int x,int y){
	int l=tree[k].l,r=tree[k].r;
	int mid=(l+r)/2;
	ll ans=1;
	if(l>=x&&r<=y)
		return tree[k].x%p;	
	if(x<=mid)
		ans=ans*find(k*2,x,y)%p;
	if(y>mid)
		ans=ans*find(k*2+1,x,y)%p;
	return ans;
}

int main(){

	scanf("%d%d%lld",&n,&m,&p);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%lld",&a[i].opt,&a[i].x);
		a[i].t=i;
		aa[i].x=a[i].x;
		aa[i].opt=a[i].opt;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		if(a[i].x!=a[i-1].x)
		{
			while(!s1.empty())
				s1.pop();
			if(a[i].opt==1)
				s1.push(a[i].t);	
		}
		else
		{
			if(a[i].opt==1)
				s1.push(a[i].t);	
			else if(!s1.empty())
			{
				int x=s1.top();
				s1.pop();
				v[a[i].t]=x;
			}
		}
	}
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&s[i].l,&s[i].r);
		s[i].id=i;
	}
	sort(s+1,s+m+1,cmp1);
	int last=0;
	for(int i=1;i<=m;i++)
	{
		while(s[i].r>last)
		{
			last++;
			if(aa[last].opt==1)
				change(1,last,aa[last].x);
			else if(v[last])
					change(1,v[last],1);
		} 
		kj[s[i].id]=find(1,s[i].l,s[i].r);
	}
	for(int i=1;i<=m;i++)
		printf("%lld\n",kj[i]);
}
/*
7 4 53453543
1 100000010
1 700000010
1 5554345
1 100240010
2 7
2 5554345
1 88
1 7
1 2
1 4
1 5
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值