P1972 [SDOI2009]HH的项链· 主席树 / 树状数组 / 线段树 / 分块

题解

代码参考这位大佬

乍看一眼题目,觉得可以前缀和可以解决
翻了众多大佬的题解,发现的确可以解决
原理·请移步这位大佬的题解

last[ i ]记录i最后一次出现的位置
根据大佬给出的样例 1 2 3 1
sum[1] - 1 0 0 0
sum[2] - 1 1 0 0
sum[3] - 1 1 1 0 统计[2,3]时就是前缀和:sum[3]-sum[2-1] = 1 1 1 0 - 1 1 0 0 = 0 1 1 0 = 2
sum[4] - 0 1 1 1 统计[2,4]时,这个时候[1,1]区间被更新了,sum[1] = 0 0 0 0,所以sum[4] - sum[2-1] = 0 1 1 1 - 0 0 0 0 = 3

可以看到更新之后就丢失了之前的信息了,所以需要离线处理 (主席树除外)

提交过程中发现代码正确但是T几个点那么零点几秒,多交几次就好,卡的过,
也许这就是没有读入挂的人的悲哀

主席树和分块T了QAQ。


在这里插入图片描述
在这里插入图片描述


离线线段树
emmm…提交的过程中发现有几个点会T那么零点几秒,多交几次就好了

#include <bits/stdc++.h>
using namespace std;
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
const int N=1e6+1;
int a[N];
int n,m,k;
struct Query{
    int l,r,id;
    void init(int x){
        scanf("%d%d",&l,&r);
        id=x;//问题编号 用于离线
    }
}q[N];
bool cmp(Query a,Query b){//根据区间右端点排排列
    if(a.r==b.r){
        return a.l<b.l;
    }
    return a.r<b.r;
}
int last[N];//标记数字最后出现的位置
int sum[N<<2];

void pushup(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int pos,int val,int l,int r,int rt){
    if(l==r){
        sum[rt]+=val;
        return;
    }
    int mid=l+r>>1;
    if(pos<=mid) update(pos,val,lson);
    else update(pos,val,rson);
    pushup(rt);//子区间更新完后要更新整个区间
}
int query(int L,int R,int l,int r,int rt){
    if(L<=l && R>=r){
        return sum[rt];
    }
    int mid=l+r>>1,res=0;
    if(L<=mid)res+=query(L,R,lson);
    if(R>mid)res+=query(L,R,rson);
    return res;
}
int ans[N];
int main(){
    scanf("%d",&n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d",&a[i]);
    }
    scanf("%d",&m);
    for (int i = 1; i <= m; ++i) {
        q[i].init(i);
    }
    sort(q+1,q+1+m,cmp);
    int cur=1;//当前已经更新到了哪个点
    for (int i = 1; i <= m; ++i) {
        for (int j = cur; j <= q[i].r; ++j) {
            if(last[a[j]]){//如果更新过 要先消除之前的标记
                update(last[a[j]],-1,1,n,1);
            }
            update(j,1,1,n,1);
            last[a[j]]=j;
        }
        cur=q[i].r+1;
        int left = q[i].l==1?0:query(1,q[i].l-1,1,n,1);
        ans[q[i].id]=query(1,q[i].r,1,n,1)-left;//前缀和
    }
    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}

离线树状数组
第一次用树状数组,嗯,比线段树的快了一倍

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int n,m,k;
struct Query{
    int l,r,id;
    void init(int i){
        scanf("%d%d",&l,&r);
        id=i;
    }
}q[N];
bool cmp(Query a,Query b){
    if(a.r==b.r){
        return a.l<b.l;
    }
    return a.r<b.r;//重要的是右端点
}
int last[N],ans[N];
/*-----树状数组------*/
int sum[N];
int lowbit(int x){
    return x & (-x);//返回 2^(x末尾0的个数)
}
void add(int pos,int val){
    for (int i = pos; i <= n; i+=lowbit(i)) {//pos~n
        sum[i]+=val;
    }
}
int getSum(int pos){
    int res=0;
    for (int i = pos; i; i-=lowbit(i)) { //1~pos
        res+=sum[i];
    }
    return res;
}
/*-----end------*/
int main(){
    scanf("%d",&n);
    for (int i = 1 ; i <= n; ++i) {
        scanf("%d",&a[i]);//种类
    }
    scanf("%d",&m);
    for (int i = 1; i <= m; ++i) {
        q[i].init(i);
    }
    sort(q+1,q+1+m,cmp);
    int cur=1;
    for (int i = 1; i <= m; ++i) {
        for (int j = cur; j <= q[i].r; ++j) {
            if(last[a[j]]){
                add(last[a[j]],-1);
            }
            add(j,1);
            last[a[j]]=j;
        }
        cur=q[i].r+1;
        ans[q[i].id]=getSum(q[i].r)-getSum(q[i].l-1);//前缀和
    }
    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}

主席树

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,k;
int last[N],a[N];
/*------------主席树----------------*/
int L[N*30],R[N*30],rt[N*30],sum[N*30];//tree
int tot;
void update(int pre,int& cur,int val,int pos,int l,int r){//原先的版本 现在的版本 传入的值 需插入的位置 左端点l 右端点r
    cur=++tot;
    L[cur]=L[pre];
    R[cur]=R[pre];
    sum[cur]=sum[pre]+val;
    if(l==r) return ;
    int mid=l+r>>1;
    if(pos<=mid) update(L[pre],L[cur],val,pos,l,mid);
    else update(R[pre],R[cur],val,pos,mid+1,r);
}

int query(int cur,int x,int y,int l,int r){
    if(x<=l && r<=y){
        return sum[cur];
    }
    int mid=l+r>>1;
    int res=0;
    if(x<=mid)res+=query(L[cur],x,y,l,mid);
    if(y>mid)res+=query(R[cur],x,y,mid+1,r);
    return res;
}
/*------------end----------------*/
int main(){
    scanf("%d",&n);
    int tmp=0;
    for (int i = 1; i <= n; ++i) {
        scanf("%d",&a[i]);
        if(last[a[i]]){
            update(rt[i-1],tmp,-1,last[a[i]],1,n);//emm tmp这个操作没看懂 以后再来琢磨琢磨
            update(tmp,rt[i],1,i,1,n);
        }else{
            update(rt[i-1],rt[i],1,i,1,n);
        }
        last[a[i]]=i;
    }
    scanf("%d",&m);
    for (int i = 1,l,r; i <= m; ++i) {
        scanf("%d%d",&l,&r);
        printf("%d\n", query(rt[r],l,r,1,n));
    }
    return 0;
}

分块

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+1;
int a[N];
int n,m,k;
struct Query{
    int l,r,id;
    void init(int x){
        scanf("%d%d",&l,&r);
        id=x;
    }
}q[N];
int block;
bool cmp(Query a,Query b){
    if(a.r/block==b.r/block)return a.l/block<b.l/block;
    return a.r/block<b.r/block;//节省时间的关键点
}
int ans[N];
int sum=0;
int cnt[N];//统计区间内每一种数字的个数
void del(int x){
    if(cnt[x]==1)sum--;
    cnt[x]--;
}
void add(int x){
    if(!cnt[x]) sum++;
    cnt[x]++;
}
int main(){
    scanf("%d",&n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d",&a[i]);
    }
    scanf("%d",&m);
    block=n/sqrt(m);//划分
    for (int i = 1; i <= m; ++i) {
        q[i].init(i);
    }
    sort(q+1,q+1+m,cmp);

    int l=0,r=0;
    for (int i = 1; i <= m; ++i) {
        while(l<q[i].l)del(a[l++]);//区间外删除
        while(l>q[i].l)add(a[--l]);//区间内增加
        while(r>q[i].r)del(a[r--]);
        while(r<q[i].r)add(a[++r]);
        ans[q[i].id]=sum;
    }

    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值