洛谷 P1972 HH的项链(树状数组,排序)

题目大意:

已知一串数字an,有m个询问,每个询问包含一个区间[a,b],问你an在区间[a,b]中有多少个不同的数。

解题思路:

我们把每个询问的右区间进行排序,每次我们对树状数组进行更新,怎么更新呢?对于重复出现的数字,我们只记录最后一次出现位置,并在对应位置打1(注意:这里不是对树状数组打1,树状数组本质上是对一个普通数组求和的维护,所以我们这里是对普通数组对应位置打1),之前打过1的位置清零。其它没出现过的数字就放0好了。什么时候更新呢?每次当询问区间的右边界往右移动的时候就开始更新,看是否有重复的数字或者第一次出现的数字。当询问区间排好序,并且询问的时候,我们是不用关心重复出现的靠左边的数字,这样做的好处是:我们将不同的数字,可以转化为区间求和问题!比如问[1,3]不同的数字,我们就可以认为是对[1,3]区间进行求和!

废话:

(1)这里使用了对询问排序这种预处理的思想可以学习。

(2)这种转换为对区间求和的思想可以学习。

#include <bits/stdc++.h>
#define LSOne(n) n&(-n)
using namespace std;
vector<pair<int,int>> range;
vector<pair<int,int>> rgh_no;
const int MAXN=1e6+10;
int flag[MAXN];
int ansarr[MAXN];
class fenwickedTree{
	private:
		vector<int> ft;
	public:
	fenwickedTree(int n){
		ft.assign(n+1,0);
	}
	void adjust(int idx,int val){
		for(;idx<(int)ft.size();idx+=LSOne(idx)){
			ft[idx]+=val;
		}
	}
	int getsum(int a,int b){
		return a==1?getsumuntill(b):getsumuntill(b)-getsumuntill(a-1);
	}
	int getsumuntill(int a){
		int sum=0;
		for(;a;a-=LSOne(a)){
			sum+=ft[a];
		}
		return sum;
	}
};
int main(){
	//ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	memset(flag,-1,sizeof(flag));
	int n;scanf("%d",&n);
	vector<int> neck;
	fenwickedTree ft(n);
	for(int i=0;i<n;i++){
		int t;scanf("%d",&t);
		neck.push_back(t);
	}
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		int a,b;scanf("%d %d",&a,&b);
		range.push_back({a,b});
		rgh_no.push_back({b,i});
	}
	sort(rgh_no.begin(),rgh_no.end(),less<pair<int,int>>());
	int poi=0;
	for(int i=0;i<(int)rgh_no.size();i++){
		if(rgh_no[i].first!=poi){
			for(int lspoi=poi+1;lspoi<=rgh_no[i].first;lspoi++){
				if(flag[neck[lspoi-1]]==-1){
					flag[neck[lspoi-1]]=lspoi;
					ft.adjust(lspoi,1);
				}else{
					ft.adjust(flag[neck[lspoi-1]],-1);
					ft.adjust(lspoi,1);
					flag[neck[lspoi-1]]=lspoi;
				}
				
			}
			poi=rgh_no[i].first;
		}
		int lf=range[rgh_no[i].second].first;
		int rg=range[rgh_no[i].second].second;
		int ans=ft.getsum(lf,rg);
		ansarr[rgh_no[i].second]=ans;
	}
	for(int i=0;i<(int)rgh_no.size();i++){
		printf("%d\n",ansarr[i]);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值