标签:莫队 问题 one play operator 树状 clu open else
题目大意:给定序列, 询问一个区间内不同颜色种数
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]); }
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]); }
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); } }
这里用主席树还有另外一种思路
单独考虑每个询问$[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)); } }
题意:给定序列, 询问区间内是否存在只出现$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]); }
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]); }
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); } }
CF 703D (较容易)
CF 765F (较容易)
CF 813E (强制在线)
CF 849E (较难)
标签:莫队 问题 one play operator 树状 clu open else
原文地址:https://www.cnblogs.com/uid001/p/10232434.html