码迷,mamicode.com
首页 > 其他好文 > 详细

区间种类数问题

时间:2019-01-07 22:37:17      阅读:233      评论:0      收藏:0      [点我收藏+]

标签:莫队   问题   one   play   operator   树状   clu   open   else   

例1 SDOI2009 HH的项链

题目大意:给定序列, 询问一个区间内不同颜色种数

 

1. 裸莫队, 奇偶分块优化

技术分享图片
#include <iostream>
#include <algorithm>
#include <math.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
const int N = 1e6+10;
int n, m, k, a[N];
int sqn, blo[N];
int tot[N];
int f, ans[N];

struct _ {
    int l,r,id;
    bool operator < (const _ & rhs) const {
        return blo[l]^blo[rhs.l]?l<rhs.l:blo[l]&1?r<rhs.r:r>rhs.r;
    }
}q[N];

void add(int x) {
    f += !tot[x]++;
}

void del(int x) {
    f -= !--tot[x];
}

int main() {
    scanf("%d", &n),sqn=pow(n,0.55);
    REP(i,1,n) scanf("%d", a+i),blo[i]=i/sqn;
    scanf("%d", &m);
    REP(i,1,m) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+1+m);
    int ql=1, qr=0;
    REP(i,1,m) {
        while (ql<q[i].l) del(a[ql++]);
        while (qr>q[i].r) del(a[qr--]);
        while (ql>q[i].l) add(a[--ql]);
        while (qr<q[i].r) add(a[++qr]);
        ans[q[i].id]=f;
    }
    REP(i,1,m) printf("%d\n",ans[i]);
}
View Code

 

2. 树状离线

种类数只考虑最后一次出现的贡献, 维护上次出现位置$lst$, 每添加一个数$c$, 减去$lst[c]$位置的贡献

技术分享图片
#include <iostream>
#include <cstdio>
#include <algorithm>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;

const int N = 1e6+10, INF = 0x3f3f3f3f;
int a[N], n, m, k, t;
int lst[N];
struct _ {int l,r,id;}q[N];
int ans[N], c[N];

void add(int x, int k) {
    for (; x<=n; x+=x&-x) c[x]+=k;
}

int qry(int x) {
    int r = 0;
    for (; x; x^=x&-x) r+=c[x];
    return r;
}

int main() {
    scanf("%d", &n);
    REP(i,1,n) scanf("%d",a+i);
    scanf("%d", &m);
    REP(i,1,m) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+1+m,[](_ a,_ b){return a.r<b.r;});
    int now = 1;
    REP(i,1,n) {
        if (lst[a[i]]) add(lst[a[i]],-1);
        add(i,1);
        lst[a[i]]=i;
        while (now<=m&&q[now].r==i) {
            ans[q[now].id]=qry(i)-qry(q[now].l-1);
            ++now;
        }
    }
    REP(i,1,m) printf("%d\n",ans[i]);
}
View Code

 

3. 主席树在线

跟离线方法类似, 每次询问在第R棵线段树上回答即可

技术分享图片
#include <iostream>
#include <algorithm>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define mid (l+r)/2
#define lc tr[o].l
#define rc tr[o].r
#define ls lc,l,mid
#define rs rc,mid+1,r
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;

const int N = 5e5+10, INF = 0x3f3f3f3f;
int n, m, tot, ans;
int rt[N], lst[N*2];
struct {int l,r,sum;} tr[N*50];

void upd(int &o, int l, int r, int pos, int v) {
    ++tot,tr[tot]=tr[o],o=tot,tr[o].sum+=v;
    if (l==r) return;
    if (mid>=pos) upd(ls,pos,v);
    else upd(rs,pos,v);
}

void qry(int o, int l, int r, int ql, int qr) {
    if (!o) return;
    if (ql<=l&&r<=qr) return ans+=tr[o].sum,void();
    if (mid>=ql) qry(ls,ql,qr);
    if (mid<qr) qry(rs,ql,qr);
}

int main() {
    scanf("%d", &n);
    REP(i,1,n) { 
        int t;
        scanf("%d", &t);
        rt[i] = rt[i-1];
        if (lst[t]) upd(rt[i],1,n,lst[t],-1);
        upd(rt[i],1,n,i,1);
        lst[t] = i;
    }
    scanf("%d", &m);
    REP(i,1,m) {
        int l, r;
        scanf("%d%d", &l, &r);
        ans = 0, qry(rt[r],1,n,l,r);
        printf("%d\n", ans);
    }
}
View Code

 

 

这里用主席树还有另外一种思路

单独考虑每个询问$[l,r]$的答案,可以得到

$\sum_{i=l}^{r}[lst_{i}<l]$

主席树维护 $lst$ 

技术分享图片
#include <iostream>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;

const int N = 5e5+10;
int n, q, tot;
int a[N], lst[N], pos[N], rt[N];

struct {int l,r,sum;} tr[N<<5];

void ins(int &o, int l, int r, int x) {
    ++tot,tr[tot]=tr[o],o=tot,++tr[o].sum;
    if (l==r) return;
    int mid = (l+r)/2;
    if (mid>=x) ins(tr[o].l,l,mid,x);
    else ins(tr[o].r,mid+1,r,x);
}

int query(int u, int v, int l, int r, int x) {
    if (l==r) return 0;
    int mid = (l+r)/2;
    if (mid>=x) return query(tr[u].l,tr[v].l,l,mid,x);
    return tr[tr[v].l].sum-tr[tr[u].l].sum+query(tr[u].r,tr[v].r,mid+1,r,x);
}

int main() {
    scanf("%d", &n);
    REP(i,1,n) {
        int t;
        scanf("%d", &t), rt[i]=rt[i-1];
        ins(rt[i],0,n,lst[t]), lst[t]=i;
    }
    scanf("%d", &q);
    while (q--) {
        int u, v;
        scanf("%d%d", &u, &v);
        printf("%d\n",query(rt[u-1],rt[v],0,n,u));
    }
}
View Code

  

 

例2 CF 1000F

题意:给定序列, 询问区间内是否存在只出现$1$次的元素, 若存在输出任意一个

 

1, 莫队+值域分块

技术分享图片
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <math.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;

const int N = 5e5+10, INF = 0x3f3f3f3f;
int n, m, sqn;
int a[N], b[N], blo[N], ans[N];
int f[N], ff[N], tot;
struct _ {
    int l,r,id;
    bool operator < (const _& rhs) const {
        return blo[l]^blo[rhs.l]?l<rhs.l:blo[l]&1?r<rhs.r:r>rhs.r;
    }
}q[N];

//f[x]记录颜色x的出现次数
//ff[x]记录值域第x块内f等于1的点的个数

void add(int x) {
    ++f[x];
    if (f[x]==1) ++ff[blo[x]],++tot;
    else if (f[x]==2) --ff[blo[x]],--tot;
}

void del(int x) {
    --f[x];
    if (f[x]==1) ++ff[blo[x]],++tot;
    else if (f[x]==0) --ff[blo[x]],--tot;
}

int calc() {
    if (!tot) return 0;
    REP(i,1,sqn) if (ff[i]) {
        REP(j,(i-1)*sqn+1,i*sqn) if (f[j]==1) return j;
    }
    return 0;
}

int main() {
    scanf("%d", &n), sqn=pow(n,0.55);
    REP(i,1,n) { 
        scanf("%d", a+i);
        blo[i]=(i-1)/sqn+1;
        b[i]=a[i];
    }
    //这里离散化为了使值域在n范围内, 块的大小能和莫队公用
    //如果块大小固定, 不离散化也可以
    sort(b+1,b+1+n),*b=unique(b+1,b+1+n)-b-1;
    REP(i,1,n) a[i]=lower_bound(b+1,b+1+*b,a[i])-b;
    *b = 0;
    scanf("%d", &m);
    REP(i,1,m) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+1+m);
    int ql=1, qr=0;
    REP(i,1,m) {
        while (ql<q[i].l) del(a[ql++]);
        while (qr>q[i].r) del(a[qr--]);
        while (ql>q[i].l) add(a[--ql]);
        while (qr<q[i].r) add(a[++qr]);
        ans[q[i].id]=b[calc()];
    }
    REP(i,1,m) printf("%d\n", ans[i]);
}
View Code

 

2, 线段树离线

类似于HH的项链, 还是只考虑每种颜色最后出现的贡献

对于询问$[L,R]$若存在一个数$x$使得$lst[x]<L$, 则$x$出现次数为1

可以线段树维护$lst$的最小值, 每次删除上次位置的贡献直接将最小值改为无穷大即可

技术分享图片
#include <iostream>
#include <cstdio>
#include <algorithm>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define mid ((l+r)/2)
#define lc (o<<1)
#define rc (lc|1)
#define ls lc,l,mid
#define rs rc,mid+1,r
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;

const int N = 5e5+10, INF = 0x3f3f3f3f;
int n, m;
int a[N], lst[N], ans[N];
struct _ {int l,r,id;}q[N];
pii mi[N<<2], t;

void build(int o, int l, int r) {
    mi[o].x = INF;
    if (l^r) build(ls),build(rs);
}

void upd(int o, int l, int r, int pos, pii v) {
    if (l==r) return mi[o]=v,void();
    if (mid>=pos) upd(ls,pos,v);
    else upd(rs,pos,v);
    mi[o] = min(mi[lc], mi[rc]);
}

void qry(int o, int l, int r, int ql, int qr) {
    if (ql<=l&&r<=qr) return t=min(t,mi[o]),void();
    if (mid>=ql) qry(ls,ql,qr);
    if (mid<qr) qry(rs,ql,qr);
}

int main() {
    scanf("%d", &n);
    REP(i,1,n) scanf("%d", a+i);
    scanf("%d", &m);
    REP(i,1,m) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+1+m,[](_ a,_ b){return a.r<b.r;});
    build(1,1,n);
    int now = 1;
    REP(i,1,n) {
        if (lst[a[i]]) upd(1,1,n,lst[a[i]],pii(INF,INF));
        upd(1,1,n,i,pii(lst[a[i]],a[i]));
        lst[a[i]] = i;
        while (now<=m&&q[now].r==i) {
            t = pii(INF,INF);    
            qry(1,1,n,q[now].l,q[now].r);
            if (t.x<q[now].l) ans[q[now].id]=t.y;
            ++now;
        }
    }
    REP(i,1,m) printf("%d\n",ans[i]);
}
View Code

 

3, 主席树, 类似于线段树做法

技术分享图片
#include <iostream>
#include <algorithm>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define mid ((l+r)>>1)
#define lc tr[o].l
#define rc tr[o].r
#define ls lc,l,mid
#define rs rc,mid+1,r
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;

const int N = 5e5+10, INF = 0x3f3f3f3f;
int n, tot, m;
int a[N], rt[N], lst[N];
pii t;
struct {int l,r;pii mi;} tr[N*80];

void build(int &o, int l, int r) {
    o = ++tot, tr[o].mi = pii(INF,INF);
    if (l^r) build(ls),build(rs);
}

void upd(int &o, int l, int r, int pos, pii v) {
    ++tot,tr[tot]=tr[o],o=tot;
    if (l==r) return tr[o].mi=v,void();
    if (mid>=pos) upd(ls,pos,v);
    else upd(rs,pos,v);
    tr[o].mi = min(tr[lc].mi,tr[rc].mi);
}

void qry(int o, int l, int r, int ql, int qr) {
    if (!o) return;
    if (ql<=l&&r<=qr) return t=min(t,tr[o].mi),void();
    if (mid>=ql) qry(ls,ql,qr);
    if (mid<qr) qry(rs,ql,qr);
}


int main() {
    scanf("%d", &n);
    build(rt[0],1,n);
    REP(i,1,n) { 
        scanf("%d", a+i);
        rt[i] = rt[i-1];
        if (lst[a[i]]) upd(rt[i],1,n,lst[a[i]],pii(INF,INF));
        upd(rt[i],1,n,i,pii(lst[a[i]],a[i]));
        lst[a[i]] = i;
    }
    scanf("%d", &m);
    REP(i,1,m) {
        int l, r;
        scanf("%d%d", &l, &r);
        t = pii(INF,INF);
        qry(rt[r],1,n,l,r);
        printf("%d\n", t.x<l?t.y:0);
    }
}
View Code

 

 

相关题目

CF 703D  (较容易)

CF 765F  (较容易)

CF 813E  (强制在线)

CF 849E  (较难)

 

区间种类数问题

标签:莫队   问题   one   play   operator   树状   clu   open   else   

原文地址:https://www.cnblogs.com/uid001/p/10232434.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!