CF B. Little Elephant and Array (暴力剪枝|莫队|线段树|树状数组)

链接

题意:

小象喜欢和数组玩。现在有一个数组 a a a,含有 n n n 个正整数,记第 i i i 个数为 a i a_i ai 。现在 m m m个询问,每个询问包含两个正整数 l j l_j lj​ 和 r j r_j rj; ( 1 ⩽ l j ⩽ r j ⩽ n ) (1\leqslant l_j\leqslant r_j\leqslant n) (1ljrjn),小象想知道在 A l j A_{l_j} Alj A r j A_{r_j} Arj ​ 之中有多少个数 x x x,其出现次也为 x x x

暴力剪枝:

说是暴力剪枝其实也不是真正的暴力,其实是前缀和优化再去剪枝。
首先我们看剪枝:一共 n n n个数,要求是区间内,这个数个数等于这个数,那么我们可以知道最多有 n \sqrt n n 个数符合条件。所以大于n 的数我们直接不用去看,他一定不会满足条件。 小于等于n的数提取出来,记录一下数量,数量大于等于这个数的我们用前缀和维护,然后每次查询我们判断这 n \sqrt n n 有多少符合条件。
时间复杂度是 O ( ( q + n ) n ) O((q+n)\sqrt n) O((q+n)n )
用int 就行 long long好像会暴内存

int n,q;
int a[maxn],num[maxn],vis[maxn],tree[500][maxn];
void solve()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(a[i]<=n){
			num[a[i]]++;
		}
	}
	int cnt=0;
	for(int i= 1 ;i<=n;i++)
	{
		if(num[i]>=i){
			vis[++cnt]=i;
			for(int j=1;j<=n;j++){
				tree[cnt][j]=tree[cnt][j-1]+(a[j]==i);
			}
		}
	}
	while(q--){
		int l,r;
		scanf("%d%d",&l,&r);
		int ans=0;
		for(int i=1;i<=cnt;i++){
			if(tree[i][r]-tree[i][l-1] == vis[i]){
				ans++;
			}
		}
		printf("%d\n",ans);
	}
}

莫队:

我们利用莫队进行离线的查询。
首先还是像上面那样的剪枝,大于n的数肯定不会有贡献,所以不用管,用莫队是因为,我们对每一段进行处理,但是我们发现这些区间有很多是重复的,那么我们就可以减少对这部分的处理。所以我们对每一段进行排序,然后处理这段,之后下一次处理利用到前一段,将其扩缩成当前这个区间即可。计算答案的条件,如果增加一段,加上这一段中数,之后这个数x的个数变成x那么答案将+1,如果个数变成x+1那么答案将-1,如果需要减掉一段数,之后某个数x的个数变成了x那么答案将+1,如果个数变成x-1那么答案将-1.
时间复杂度 O ( q l o g q + n n ) O(qlog_q+n\sqrt n) O(qlogq+nn )

这是没有分块的优化:3800ms左右 差点超时 (也可以说是纯暴力了时间还没上面那个优)

int n,m,res;
int a[maxn],ans[maxn],num[maxn];
struct node {
	int l,r;
	int id;
}q[maxn];
bool cmp(node a,node b){
	if(a.l!=b.l) return a.l<b.l;
	return a.r<b.r;
}
void Insert(int x){
	if(a[x] > n) return ;
	++num[a[x]];
	if(num[a[x]]==a[x]) ++res;
	if(num[a[x]]==a[x]+1) --res;
}
void Delete(int x){
	if(a[x]>n) return ;
	if(num[a[x]]== a[x]+1) res++;
	if(num[a[x]]==a[x]) res--;
	num[a[x]]--;
}
void solve(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		int x =q[i].l,y=q[i].r,id=q[i].id;
		while(r<y) Insert(++r);
		while(l>x) Insert(--l);
		while(r>y) Delete(r--);
		while(l<x) Delete(l++);
		ans[id]= res;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

基本的莫队:


int n,m,res,k;
int a[maxn],ans[maxn],num[maxn];
struct node {
	int l,r;
	int id;
}q[maxn];
bool cmp(node a,node b){
	
	if(a.l/k==b.l/k) return a.r<b.r;
	return a.l/k<b.l/k;
}
void Insert(int x){
	if(a[x] > n) return ;
	++num[a[x]];
	if(num[a[x]]==a[x]) ++res;
	if(num[a[x]]==a[x]+1) --res;
}
void Delete(int x){
	if(a[x]>n) return ;
	if(num[a[x]]== a[x]+1) res++;
	if(num[a[x]]==a[x]) res--;
	num[a[x]]--;
}
void solve(){
	scanf("%d%d",&n,&m);
	k=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		int x =q[i].l,y=q[i].r,id=q[i].id;
		while(r<y) Insert(++r);
		while(l>x) Insert(--l);
		while(r>y) Delete(r--);
		while(l<x) Delete(l++);
		ans[id]= res;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

树状数组:

首先了解下树状数组,可维护区间和,我们这个题是找区间贡献值的。所以可行,只不过需要加工。
怎么加工那?
首先我们树状数组查询答案,是 s[r]-s[l-1],
如果我们有一个数x是这样的
1 2 3 4 5 6 ..... y
x x x x x x ..... x
这样的话我们从最后一个x看,他产生贡献的左端点是y-x+1,因为我们记录的是差那么y-x这个位置上我们为-1即可,然后我们对新生成的数组进行差分,得到y-x位置上为-1,y-x+1位置上为1.说是差分,也可以理解为记录y对应的左端点,然后我们看前一个状态 右端点为y-1那么同样的 y-1-x位置上为-1,y-x位置上为1,我们从 右端点为y-1到y 其实是y-1-x位置上-1变成0,所以需要+1, y-x位置上1变成-1,所以需要-2,y-x+1位置上,从0变成1所以需要+1.
这样构建树状数组即可查询。

vector <pii> q[maxn];
vector <int> g[maxn];
int n,m;
int a[maxn],ans[maxn],tree[maxn];

void update(int pos,int num){
 	while(pos <= n){
 		tree[pos]+=num;
 		pos+=lowbit(pos);
 	}
}

int query (int pos){
	int sum = 0;
	while (pos){
		sum+=tree[pos];
		pos-=lowbit(pos);
	}
	return sum;
}

void solve(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);		
	}
	for(int i=1;i<=m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		q[r].push_back({l,i});
	}
	for(int i=1;i<=n;i++){
		int x = a[i];
		if(x>n) continue;
		g[x].push_back(i);
		int size = g[x].size();
		if(size >=x){
			update(g[x][size-x],1);
			if(size>=x+1) update(g[x][size-x-1],-2);
			if(size>=x+2) update(g[x][size-x-2],1);
		}
		for(int j=0;j<q[i].size();j++){
			pii p=q[i][j];
			ans[p.y]=query(i)-query(p.x-1);
		}
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值