[SDOI2009]HH的项链

链接:https://ac.nowcoder.com/acm/contest/19684/B

[SDOI2009]HH的项链

HH有一串由各种漂亮的贝壳组成的项链。
HH相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一 段贝壳,思考它们所表达的含义。
HH不断地收集新的贝壳,因此他的项链变得越来越长。
有一天,他突然提出了一 个问题:某一段贝壳中,包含了多少种不同的贝壳?
这个问题很难回答。。。因为项链实在是太长了。于是,他只 好求助睿智的你,来解决这个问题。

输入描述:
第一行:一个整数N,表示项链的长度。
第二行:N个整数,表示依次表示项链中贝壳的编号(编号为0到1000000之间的整数)。
第三行:一个整数M,表示HH询问的个数。
接下来M行:每行两个整数,L和R(1 ≤ L ≤ R ≤ N),表示询问的区间。
N ≤ 50000,M ≤ 200000。
输出描述:
M行,每行一个整数,依次表示询问对应的答案。
示例1
输入
复制
6
1 2 3 4 3 5
3
1 2 
3 5
2 6
输出
复制
2
2
4

备注:
对于20%的数据,1\le n,m\leq 50001≤n,m≤5000
对于40%的数据,1\le n,m\leq 10^51≤n,m≤10 5
对于60%的数据,1 \le n,m\leq 5\times 10^51≤n,m≤5×105
对于100%的数据,1\le n,m,a_i \leq 10^6,1\le l \le r \le n1≤n,m,a i≤10 6 ,1≤l≤r≤n。
本题可能需要较快的读入方式,最大数据点读入数据约 20MB

本题是一道莫队算法的题目,但是同时可以使用树状数组,线段树或者主席树来解决

由于本人莫队算法不精,就写一下离线的树状数组写法和在线的主席树:

树状数组

大致思路就是
1.先读入所有数据并按照区间右端点排序
2.从小到大遍历右端点,对于每次固定的右端点,我们按照左端点来依次查询
3.查询方式:记录每一个值在此之前出现的位置,当遍历到某一位置时,将其上一个位置处的值-1,此处值+1,这样从开头到当前点的前缀和即为种类个数。
如果仍然不理解可以看图:
在这里插入图片描述
AC代码小贴士:空间一定要开到大于1e6

#include<bits/stdc++.h>
using namespace std;
#define N 50010
#define M 1000010

struct Ques{
    int id;
    int l,r;
    bool operator < (const Ques W) const{
        return r<W.r;
    }
}ques[M];
int n,m;
int w[M],last[M];
int ans[M];
int tr[M];

int lowbit(int x){
    return x&-x;
}

void add(int u,int x){
    for(int i=u;i<=n;i+=lowbit(i)){
        tr[i]+=x;
    }
}

int ask(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
    return res;
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    
    //build();
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int l,r;
        scanf("%d%d",&ques[i].l,&ques[i].r);
        ques[i].id=i;
    }
    
    sort(ques+1,ques+1+m);
    
    for(int i=1,r=0;i<=m;i++){
        while(r<ques[i].r){
            r++;
            if(last[w[r]])
                add(last[w[r]],-1);
            add(r,1);
            last[w[r]]=r;
        }
        ans[ques[i].id]=ask(r)-ask(ques[i].l-1);
    }
    
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    
    return 0;
}
看到区间查询就想到可持久化线段树,然后就可以尝试用主席树来做一下了

其实主席树的思路不是很好想,我是借鉴了这位博主的思路:
https://blog.csdn.net/weixin_45799835/article/details/116649809

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E4 + 10, M = 2E5 + 10, QAQ = 1E6 + 10, L = 0, R = N - 5; // 测的题不同, 所以M变为了2E5.
int last[QAQ];
struct node {
	int l, r;
	int val;
}t[N << 6];
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) {
	int x = ++ind; t[x] = t[p];
	t[x].val += c;
	if (tl == tr) return x;
	int mid = tl + tr >> 1;
	if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
	else t[x].r = build(a, c, mid + 1, tr, t[p].r);
	return x;
}
int ask(int l, int r, int tl, int tr, int p, int x) {
	if (l <= tl and r >= tr) return t[x].val - t[p].val;
	int mid = tl + tr >> 1;
	int res = 0;
	if (l <= mid) res += ask(l, r, tl, mid, t[p].l, t[x].l);
	if (r > mid) res += ask(l, r, mid + 1, tr, t[p].r, t[x].r);
	return res;
}
int main()
{
	int n; cin >> n;
	rep(i, n) {
		int col; scanf("%d", &col);
		root[i] = build(last[col], 1, L, R, root[i - 1]);
		last[col] = i;
	}

	int m; cin >> m;
	rep(i, m) {
		int l, r; scanf("%d %d", &l, &r);
		printf("%d\n", ask(0, l - 1, L, R, root[l - 1], root[r]));
	}

	return 0;
}

扩展:
https://ac.nowcoder.com/acm/contest/19684/C
当扩展至两个的时候,只需要将last数组开成二维即可,很像一些dp还有次小生成树的意思。

#include<bits/stdc++.h>
using namespace std;
#define N 50010
#define M 1000010

struct Ques{
    int id;
    int l,r;
    bool operator < (const Ques W) const{
        return r<W.r;
    }
}ques[M];
int n,m,c;
int w[M],last[M][2];
int ans[M];
int tr[M];

int lowbit(int x){
    return x&-x;
}

void add(int u,int x){
    for(int i=u;i<=n;i+=lowbit(i)){
        tr[i]+=x;
    }
}

int ask(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
    return res;
}

int main(){
    scanf("%d%d%d",&n,&c,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    
    //build();
    
    for(int i=1;i<=m;i++){
        int l,r;
        scanf("%d%d",&ques[i].l,&ques[i].r);
        ques[i].id=i;
    }
    
    sort(ques+1,ques+1+m);
    
    for(int i=1,r=0;i<=m;i++){
        while(r<ques[i].r){
            r++;
            if(last[w[r]][1]){
                add(last[w[r]][1],-1);
                add(last[w[r]][0],1);
            }
            else if(last[w[r]][0])
                add(last[w[r]][0],1);
            last[w[r]][1]=last[w[r]][0];
            last[w[r]][0]=r;
        }
        ans[ques[i].id]=ask(r)-ask(ques[i].l-1);
    }
    
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值