BZOJ 1878 HH的项链(主席树)

对于该题,离线的做法是树状数组或者线段树。

如果强制在线的话,可以用主席树做到O(mlogn)。

考虑到这样一个性质,对于询问[l,r]出现的数字种数。其答案就是to[i]>r的数字数。 其中to[i]表示的是第i个数的下一个相同的数出现的下标,没有则=n+1.

很幸运这个性质是满足区间减法的,也就是说对于[1,r]和[1,l-1]的to[i]域,是可以相减得到[l,r]的to[i]域的。

于是我们可以用主席树来解决这个问题。

对于一组询问,实际上就是求[l-1,r]这颗线段树上的区间[r+1,n+1]的出现次数总和。

# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <vector>
# include <queue>
# include <stack>
# include <map>
# include <set>
# include <cmath>
# include <algorithm>
using namespace std;
# define lowbit(x) ((x)&(-x))
# define pi 3.1415926535
# define eps 1e-9
# define MOD 1000000009
# define INF 1000000000
# define mem(a,b) memset(a,b,sizeof(a))
# define FOR(i,a,n) for(int i=a; i<=n; ++i)
# define FO(i,a,n) for(int i=a; i<n; ++i)
# define bug puts("H");
# define lch p<<1,l,mid
# define rch p<<1|1,mid+1,r
# define mp make_pair
# define pb push_back
typedef pair<int,int> PII;
typedef vector<int> VI;
# pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long LL;
int Scan() {
    int res=0, flag=0;
    char ch;
    if((ch=getchar())==‘-‘) flag=1;
    else if(ch>=‘0‘&&ch<=‘9‘) res=ch-‘0‘;
    while((ch=getchar())>=‘0‘&&ch<=‘9‘)  res=res*10+(ch-‘0‘);
    return flag?-res:res;
}
void Out(int a) {
    if(a<0) {putchar(‘-‘); a=-a;}
    if(a>=10) Out(a/10);
    putchar(a%10+‘0‘);
}
const int N=50005;
//Code begin...

int root[N], s[N*80], ls[N*80], rs[N*80], sz, vis[1000005], to[N];

void insert(int l, int r, int x, int &y, int val){
    y=++sz;
    s[y]=s[x]+1;
    if (l==r) return ;
    ls[y]=ls[x]; rs[y]=rs[x];
    int mid=(l+r)>>1;
    if (val<=mid) insert(l,mid,ls[x],ls[y],val);
    else insert(mid+1,r,rs[x],rs[y],val);
}
int query(int l, int r, int x, int y, int L){
    if (r<L) return 0;
    if (l>=L) return s[y]-s[x];
    int mid=(l+r)>>1;
    return query(l,mid,ls[x],ls[y],L)+query(mid+1,r,rs[x],rs[y],L);
}
int main()
{
    int n, m, l, r, x;
    scanf("%d",&n);
    FOR(i,1,n) {
        scanf("%d",&x);
        if (vis[x]) to[vis[x]]=i;
        vis[x]=i;
    }
    FOR(i,1,n) if (to[i]==0) to[i]=n+1;
    FOR(i,1,n) insert(1,n+1,root[i-1],root[i],to[i]);
    scanf("%d",&m);
    FOR(i,1,m) {
        scanf("%d%d",&l,&r);
        printf("%d\n",query(1,n+1,root[l-1],root[r],r+1));
    }
    return 0;
}

时间: 2024-08-26 06:45:34

BZOJ 1878 HH的项链(主席树)的相关文章

bzoj 1878 HH的项链 (树状数组+离线)

题目大意:给你一个序列,求某区间出现不同的数的个数. 貌似离线树状数组是最好的解法 先把所有询问挂在它们询问的右端点上 然后从头到尾遍历这个序列,记录这个位置的值上一次出现的位置 那么,当遍历到第i位时,如果a[i]在之前出现过,就在它上一次出现的位置-1 这个操作的意义是,第i位已经有a[i]了,那么上一次出现a[i]的位置已经失去意义 接着在这个位置+1,更新last[a[i]].差分操作用树状数组维护 然后我们遍历以这个位置为结尾的所有询问,用树状数组查前缀和,因为在a[i]相同的位置不会

BZOJ 1878 HH的项链

不能分块(显然复杂度会炸啊.....) 离线+BIT.每个颜色在每个询问中只出现一次. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 50050 #define maxm 200050 #define maxc 1000500 using namespace std; int n,a[maxn],aft[maxn],pre[maxn

[bzoj] 1878 HH的项链 || 莫队

原题 给定长为 n 的一个序列,接下来 m 次询问,每次询问区间 [ l , r ] 内有多少个不同的数. 莫队: 离线\(O(n\log(n))\). 将序列分块. 以左端点所在块为第一关键字,右端点位置为第二关键字sort,然后two-points移动暴力记录即可. #include<cstdio> #include<algorithm> #include<cmath> #define N 50010 #define M 200010 using namespace

[BZOJ 4571][Scoi2016]美味(主席树)

Description 一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 ai(1≤i≤n).有 m 位顾客,第 i 位顾客的期 望值为 bi,而他的偏好值为 xi .因此,第 i 位顾客认为第 j 道菜的美味度为 bi XOR (aj+xi),XOR 表示异或 运算.第 i 位顾客希望从这些菜中挑出他认为最美味的菜,即美味值最大的菜,但由于价格等因素,他只能从第 li 道到第 ri 道中选择.请你帮助他们找出最美味的菜. Solution 按位贪心,每次通过查询一段区间是

【BZOJ 3772】精神污染 主席树+欧拉序

这道题的内存-------真·精神污染---.. 这道题的思路很明了,我们就是要找每一个路径包含了多少其他路径那么就是找,有多少路径的左右端点都在这条路径上,对于每一条路径,我们随便选定一个端点作为第一关键字,另一个作为第二关键字,于是就有了两维限制,按照主席树的一般思路,我们把建树顺序作为一维,然后在里面维护另一维,那么我们在外面限制第一关键字,就是在树上建主席树,查询减LCA,在里面的话我们把每个点作为第一关键字对应的第二关键字,放入主席树,而主席树维护的是欧拉序区间,所以我们每次查询只用查

【BZOJ 3123】 [Sdoi2013]森林 主席树启发式合并

我们直接按父子关系建主席树,然后记录倍增方便以后求LCA,同时用并查集维护根节点,而且还要记录根节点对应的size,用来对其启发式合并,然后每当我们合并的时候我们都要暴力拆小的一部分重复以上部分,总时间复杂度为O(n*log),因为每个的节点只会作为小的部分合并,因此他所在的一小部分至少变大2倍,对于每一个节点他作为被合并方最多log次,因此其复杂度为O(n*log),而这个是绝对跑不满还差很多的,我们视他为无常数就好了,当然这一切都是建立在无拆分操作的基础之上,只要有了拆分启发式合并的复杂度就

BZOJ 2006 超级钢琴(堆+主席树)

很好的一道题. 题意:给出长度为n的数列,选择k个互不相同的区间,满足每个区间长度在[L,R]内,求所有选择的区间和的总和最大是多少.(n,k<=5e5). 首先将区间和转化为前缀和之差,那么我们想要区间和的总和最大,一个朴素的想法是把所有满足限制的区间和排序,取最大的k个. 考虑每个右端点i,其中有效的左端点是[i-R+1,i-L+1].如果我们对于每个右端点都找到"当前"最大的区间和,那么把它们扔进大根堆里维护一下即可. 由于sum[i]一定,那么只要左端点的sum值越小越好

BZOJ 3524 POI 2014 Couriers 主席树

题目大意 给出一个序列,问一段区间内有没有出现过一半以上的数字. 思路 用主席树取区间出来,在权值线段树上找. CODE #define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 500010 #define MAXR 10000010 using namespace std

bzoj 1901: Zju2112 Dynamic Rankings -- 主席树,树状数组,哈希

1901: Zju2112 Dynamic Rankings Time Limit: 10 Sec  Memory Limit: 128 MB Description 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1 ],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改 变后的a继续回答上面的问题. Input 第一行有两个正整数n(1≤