2018 牛客网暑期ACM多校训练营(第一场)J.Different Integers(树状数组+区间数字种数)

题目

n(n<=1e5)个数,第i个整数ai范围[1,n]

q(q<=1e5)个询问,每次给出Li,Ri,询问[1,Li]∪[Ri,n]中出现的数的种类

思路来源

凡神

后续

https://ac.nowcoder.com/acm/contest/1084/B 原题警告1

https://codeforces.com/contest/1288/problem/E 原题警告2

按右端点增序,贪心地越右越好,原理一致的,双指针写法

在线处理2*n个位置,把上个位置删掉,把当前位置加回来,只用last一个数组

是常数最小的写法,也更简洁,根本不用处理head数组和next数组

题解

[1,l]和[r,n],先把数组复制一遍,

也就是[r,l+n]的答案了,排序后成为新的[L,R]后处理询问

对于L增序询问,初始L=1,

贪心地选择,选择值出现的第一个位置,越左越好

每删掉一个值的第一个位置(L大于这个位置),

就把这个值的下一个位置加到树状数组里

树状数组里对答案产生贡献的,始终是每个值出现的第一个位置

不在区间询问里的,自然作差就没了

所以需要倒序预处理,每个值出现的首位置和下一个出现的位置

代码1(自己写的)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,q,a[maxn*2];
int tree[maxn*2];
int head[maxn],nex[2*maxn];
int l,r; 
bool vis[maxn];
//head[a[i]]:a[i]这个值第一次出现的位置
//next[i]:与i位置相同的值下一次出现的位置 
int ans[maxn];
struct node
{
	int l,r,id;
	node(){} 
	node(int L,int R,int i):l(L),r(R),id(i){} 
}e[maxn];
bool operator<(node a,node b)
{
	return a.l<b.l||(a.l==b.l&&a.r<b.r);
}
void add(int x,int v)
{
	for(int i=x;i<=n;i+=i&-i)
	tree[i]+=v;
} 
int sum(int x)
{
	int ans=0;
	for(int i=x;i>0;i-=i&-i)
	ans+=tree[i];
	return ans;
}
int main()
{
	while(~scanf("%d%d",&n,&q))
	{
	 for(int i=1;i<=n;++i)
	 {
	  scanf("%d",&a[i]);
	  a[i+n]=a[i];
	  head[i]=2*n+1;
	  vis[i]=0;
     }
     for(int i=1;i<=q;++i)
	 {
		scanf("%d%d",&l,&r);
		e[i]=node(r,l+n,i);
	 }
	 n*=2;
     for(int i=n;i>=1;--i)
     {
      nex[i]=head[a[i]];
	  head[a[i]]=i;
	  tree[i]=0; 
     }
	 sort(e+1,e+q+1); 
	 int L=1;
	 for(int i=1;i<=n;++i)
	 if(!vis[a[i]])
	 {
	 	vis[a[i]]=1;
	 	add(i,1);
	 }
	 for(int i=1;i<=q;++i)
	 {
	 	while(L<e[i].l)
	 	{
	 		if(nex[L]<=n)add(nex[L],1);
	 		L++;
	 	}
	 	ans[e[i].id]=sum(e[i].r)-sum(e[i].l-1);
	 }
	 for(int i=1;i<=q;++i)
	 printf("%d\n",ans[i]); 
    }
	 return 0;
}

代码2(学习)

#include <algorithm>
#include <cstdio>
 
using namespace std ;
 
typedef long long LL ;
const int N=5e5+10 ;
 
struct Query
{
    int l , r , id ;
    bool operator < ( const Query &q ) const { return r<q.r; }
};
Query q[N] ;
int a[N] , lst[N] ;
LL ans[N] ;
int n , m ;
 
LL C[N] ;
inline void update ( int x , LL d ) { while ( x<=n ) C[x]+=d,x+=x&-x; }
inline LL query ( int x ) { LL res=0; while ( x>0 ) res+=C[x],x-=x&-x; return res; }
 
int main ()
{
    int i , j ;
    while(~scanf("%d%d",&n,&m)){
    for ( i=1 ; i<=n ; i++ ) scanf("%d",a+i),a[n+i]=a[i];
    for ( i=1 ; i<=m ; i++ ) scanf("%d%d",&q[i].l,&q[i].r),q[i].l+=n,swap(q[i].l,q[i].r),q[i].id=i;
    sort(q+1,q+m+1);
    n*=2;
    for ( i=j=1 ; i<=n ; i++ )
    {
        if ( lst[a[i]] ) update(lst[a[i]],-1);
        lst[a[i]]=i,update(i,1);
        while ( q[j].r==i && j<=m ) ans[q[j].id]=query(i)-query(q[j].l-1),j++;
    }
    for ( i=1 ; i<=m ; i++ ) printf("%lld\n",ans[i]);
    for ( i=1 ; i<=n ; i++ ) C[i]=lst[a[i]]=0;
    }
    return 0 ;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Code92007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值