牛客第一场J题 题解

题目大意是给出n个数,对于每次查询(l,r),求出(1,l)和(r,n)两个区间不同的数有几个。

   第一步,先考虑把两个区间的问题,化成一个区间内不同数的个数:

    把原数组复制一遍贴到原数组的末尾,这样数组长度变成2n,查询区间变成了(r,n+l)。

 接着,对于如何求一个区间内不同数的个数,可以使用树状数组进行维护。看得懂模板,会使用即可。

  有关树状数组:https://blog.youkuaiyun.com/rentenglong2012/article/details/69230308

                           https://blog.youkuaiyun.com/flushhip/article/details/79165701

树状数组模板:

class TA//树状数组模板
{
private:
	int len;
	int lb(int k)    //取某个数二进制表示中最后一个1
	{
		return k&(-k);//or  int t=k ;  t&=(t-1);  k=k-t;
	}
public:
	int e[N];
	void add(int x, int v)//加元素
	{
		while (x <= len)
		{
			e[x] += v;
			x += lb(x);
		}
	}
	void init(int* getin, int _len)//数组初始化
	{
		len = _len;
		for (int i = 1; i <= len; i++)
		{
			add(i, *(getin + i));
		}
	}
	int query(int x)//查询0到x的区间和
	{
		int sum = 0;
		while (x>0)
		{
			sum += e[x];
			x -= lb(x);
		}
		return sum;
	}

};

 主要用两个数组first(标记数第一次出现的位置)和nxt(存这个数下一次出现的位置);

从1到2n遍历一遍,初始化first和nxt,并将first存入树状数组中,区间之间除了0就是1,所以区间中不同整数的个数便是树状数组的区间和。

接着对于q个查询区间进行升序排序。

从1遍历到第一个查询区间的左端点,过程中,对于first[i]=1,将first[i]变为0,first[nxt[i]]变为1,并更新树状数组。

计算区间和,保存结果,接着从第一个左端点遍历到第二个左端点,重复以上操作 ,直至q次操作完。

AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;

struct Q {
	int l, r, id;
	bool operator < (const Q &b) const {
		return this->l < b.l;
	}
}qry[N];
class TA//树状数组模板
{
private:
	int len;
	int lb(int k)    //取某个数二进制表示中最后一个1
	{
		return k&(-k);//or  int t=k ;  t&=(t-1);  k=k-t;
	}
public:
	int e[N];
	void add(int x, int v)//加元素
	{
		while (x <= len)
		{
			e[x] += v;
			x += lb(x);
		}
	}
	void init(int* getin, int _len)//数组初始化
	{
		len = _len;
		for (int i = 1; i <= len; i++)
		{
			add(i, *(getin + i));
		}
	}
	int query(int x)//查询0到x的区间和
	{
		int sum = 0;
		while (x>0)
		{
			sum += e[x];
			x -= lb(x);
		}
		return sum;
	}

};
int a[N];
int last[N];//标记数上一次出现的位置,辅助计算nxt;
int first[N];//标记数第一次出现的位置
int vis[N];//判断数字是否第一次出现;
int nxt[N];//存数下一次出现的位置
int ans[N];//保存查询结果;
int main()
{

	int n, q;
	TA acm;
	while (scanf("%d %d", &n, &q) == 2)
	{
		memset(last, 0, sizeof(last));//数组初始化;
		memset(first, 0, sizeof(first));
		memset(vis, 0, sizeof(vis));
		memset(nxt, -1, sizeof(nxt));
		memset(acm.e, 0, sizeof(acm.e));
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			a[i + n] = a[i];//数组增加,两个区间变一个区间;
		}
		n *= 2;
		//遍历赋值first数组和nxt数组;
		for (int i = 1; i <= n; i++) {
			if (!vis[a[i]])
			{
				vis[a[i]] = 1;
				first[i] = 1;
			}
			nxt[last[a[i]]] = i;
			last[a[i]] = i;
		}
		for (int i = 0; i<q; i++)//输入查询区间
		{
			scanf("%d %d", &qry[i].l, &qry[i].r);
			qry[i].l += n / 2;//l=l+n;
			swap(qry[i].l, qry[i].r);//交换l,r位置;
			qry[i].id = i;//标记;

		}
		acm.init(first, n);//将first数组弄成树状数组;
		int now = 1;
		sort(qry, qry + q);//将查询区间排序;
		for (int i = 0; i<q; i++)
		{

			while (now<qry[i].l)//更新树状数组

			{
				if (first[now])
				{
					first[now] = 0;
					acm.add(now, -1);
					first[nxt[now]] = 1;//更新1的位置
					acm.add(nxt[now], 1);


				}
				now++;
			}
			//cout<<acm.query(qry[i].r)<<" "<<acm.query(qry[i].l-1)<<endl;
			ans[qry[i].id] = acm.query(qry[i].r) - acm.query(qry[i].l - 1);//算区间之间有几个1;
		}
		for (int i = 0; i<q; i++)
		{
			printf("%d\n", ans[i]);
		}

	}

	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值