zoukankan      html  css  js  c++  java
  • [AHOI2013]作业 值域分块+莫队

    题目描述

    此时己是凌晨两点,刚刚做了 (Codeforces) 的小 (A) 掏出了英语试卷。英语作业其实不算多,一个小时刚好可以做完。然后是一个小时可以做完的数学作业,接下来是分别都是一个小时可以做完的化学,物理,语文……小 (A) 压力巨大。

    这时小 (A) 碰见了一道非常恶心的数学题,给定了一个长度为 (n) 的数列和若干个询问,每个询问是关于数列的区间表示数列的第 (l) 个数到第 (r) 个数),首先你要统计该区间内大于等于 (a),小于等于 (b) 的数的个数,其次是所有大于等于 (a),小于等于 (b) 的,且在该区间中出现过的数值的个数。

    (A) 望着那数万的数据规模几乎绝望,只能向大神您求救,请您帮帮他吧。

    输入格式

    第一行两个整数 (n,m)

    接下来 (n) 个不超过 (10^5) 的正整数表示数列

    接下来 (m) 行,每行四个整数 (l,r,a,b),具体含义参见题意。

    输出格式

    输出 (m) 行,分别对应每个询问,输出两个数,分别为在 (l)(r) 这段区间中大小在 ([a,b]) 中的数的个数,以及大于等于 (a),小于等于 (b) 的,且在该区间中出现过的数值的个数(具体可以参考样例)。

    输入输出样例

    输入

    3 4
    1 2 2
    1 2 1 3
    1 2 1 1
    1 3 1 3
    2 3 2 3

    输出

    2 2
    1 1
    3 2
    2 1

    说明/提示

    (Nleqslant 100000,Mleqslant 100000),读入的数字均为 ([1,10^5]) 内的正整数。

    分析

    写了这个题感觉对莫队和分块理解深了许多(暴力算法妙啊!!)

    首先看到区间查询,友好又好写的莫队当然是首要选择。其次我们看到,这个题的询问不同平常的莫队,答案要输出当前区间范围内,一个值域范围内有几种数和几个数,那么我们考虑如何快速计算当前区间范围内,有多少个值满足要求,那么这就要求我们在查询的时候,不仅仅需要更新区间,还需要查询某一个值域范围内的东西。显然可以用树状数组写,但是个人感觉维护起来不好写,而且会被卡……所以我们需要用到毒瘤的值域分块来进行维护。

    首先是莫队,不会的可以看我之前的笔记(打广告)。重点在于我们在跳区间的时候如何修改,还有最后如何查询答案。由于每一次询问的 ([a,b]) 是一个区间,所以我们就可以把分块按值域开,然后我们开一个 (cnt) 数组用来记录每个值出现的次数,然后再用两个记录数组来分别记录当前值域块内有几种数和几个数。

    在每一次区间边界改变的时候,需要加入当前数的话,我们先判断一下之前当前数有几个,如果为 (0) ,那么当前值所在的块的数的种类数就加 (1) ,然后给当前块的数总数加 (1) 。删除的时候也一样,如果 (cnt[i]--) 后为 (0) ,那么证明这个数在当前区间内没了,那么就在块的种类数里减 (1) 。最后我们只需要用分块的思想对于 ([a,b]) 区间进行统计答案即可。

    需要注意的是,我们块是按照值域开的。所以要找到查询的值域和数列中的最大值进行分块,如果直接在数列中最大值进行分块的话,那么查询如果超过这个值,那么块的所属就会是 (0) ,那么统计答案就会挂(因为这个调了半个下午,感谢 lgx 巨佬),所以要取所有值域的最大值(可能这个问题是题库中数据问题,但是题目没有明确说明查询区间是多大,所以还是谨慎为好,不考虑这个问题在洛谷上是可以 (AC) 的)。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 3e6+10;
    #define gc() (p1 == p2 ? (p2 = buf + fread(p1 = buf, 1, 1 << 20, stdin), p1 == p2 ? EOF : *p1++) : *p1++)
    #define read() ({ register int x = 0, f = 1; register char c = gc(); while(c < '0' || c > '9') { if (c == '-') f = -1; c = gc();} while(c >= '0' && c <= '9') x = x * 10 + (c & 15), c = gc(); f * x; })
    char buf[1 << 20], *p1, *p2;
    int a[maxn];
    int n,m;
    int jl1[maxn],jl2[maxn];//ji1为种类数,jl2为数的个数之和
    int sum;
    int cnt[maxn];
    int ans1[maxn],ans2[maxn];
    int block,bel[maxn];
    struct Node{//离线询问
    	int l,r,a,b,id;
    	Node(){}
    	Node(register int x,register int y,register int c,register int d,register int e){
    		l = x,r = y,a = c,b = d,id = e;
    	}
    	friend bool operator < (register const Node& x,register const Node& y){//按块排序
    		return bel[x.l] == bel[y.l] ? x.r < y.r : bel[x.l] < bel[y.l];
    	}
    }q[maxn];
    struct Q{//开结构体记录答案
    	int sum,lei;
    	Q(){}
    	Q(int a,int b){
    		sum = a,lei = b;
    	}
    };
    inline void Add(int pos){//加入一个数
    	if(!cnt[a[pos]])jl1[bel[a[pos]]]++;//如果之前没有就种类++
    	jl2[bel[a[pos]]]++;//数的总数++
    	cnt[a[pos]]++;
    }
    inline void Del(int pos){//删除一个数
    	cnt[a[pos]]--;
    	if(!cnt[a[pos]])jl1[bel[a[pos]]]--;//删除后没了,当前块种类数--
    	jl2[bel[a[pos]]]--;//当前块数的总数--
    }
    inline Q query(int l,int r){//分块查询值域范围内的答案
    	int su = 0,le = 0;
    	if(bel[l] == bel[r]){
    		for(int i = l;i <= r;++i){
    			if(cnt[i])le++;
    			su += cnt[i];
    		}
    	}
    	else{
    		for(int i = l;i <= bel[l] * block;++i){
    			if(cnt[i])le++;
    			su += cnt[i];
    		}
    		for(int i = (bel[r] - 1) * block + 1;i <= r;++i){
    			if(cnt[i])le++;
    			su += cnt[i];
    		}
    		for(int i = bel[l] + 1;i <= bel[r] - 1;++i){
    			le += jl1[i];
    			su += jl2[i];
    		}
    	}
    	return Q(su,le);//以结构体类型返回,方便找答案(不用写两个查询)
    }
    
    int main(){
    	n = read(),m = read();
    	block = sqrt(n);
    	int mx = 0;
    	for (register int i = 1; i <= n; i++) a[i] = read(),bel[i] = (i - 1) / block + 1;
    	for (register int i = 1; i <= m; i++) {
    		register int l = read(),r = read(),a = read(),b = read();
    		mx = max(mx,b);//这里要取max!!!!!
    		q[i] = Node(l,r,a,b,i);
    	}
    	sort(q+1,q+m+1);
    	block = sqrt(mx);
    	for(int i = 1;i <= mx;++i){
    		bel[i] = (i - 1) / block + 1;
    	}
    	int l = 1,r = 0;
    	for (register int i = 1; i <= m; i++) {//以下是莫队基操
    		int ql = q[i].l,qr = q[i].r,qa = q[i].a,qb = q[i].b;
    		while(l < ql)Del(l++);
    		while(l > ql)Add(--l);
    		while(r < qr)Add(++r);
    		while(r > qr)Del(r--);
    		Q Ans = query(qa,qb);//查询值域内的答案
    		ans1[q[i].id] = Ans.sum;
    		ans2[q[i].id] = Ans.lei;
    	}
    	for (int i = 1; i <= m; i++) {//输出答案
    		printf("%d %d
    ",ans1[i],ans2[i]);
    	}
    	return 0;
    }
    

    (Never Give Up)

  • 相关阅读:
    JavaScript--Function类型(11)
    CSS--清除浮动
    JavaScript--正则表达式(笔记)
    JavaScript--模块化编程(笔记)
    原生javascript-图片滚动按需加载
    原生javascript-图片按钮切换
    原生javascript-图片弹窗交互效果
    对CSS了解-overflow:hidden
    对CSS了解-选择器权重
    TaskTimer
  • 原文地址:https://www.cnblogs.com/Vocanda/p/13831987.html
Copyright © 2011-2022 走看看