【BZOJ】1878 HH的项链
2016-01-23 23:39
260 查看
Overview
求区间不同数的个数。N≤50000N\leq 50000,M≤200000M\leq 200000
Analysis
1. 莫队算法
多个区间询问,有在线和离线的方法。先考虑离线吧。按照块排序,使用莫队算法可以轻松解决。
时间复杂度:O(nn−√)O(n\sqrt n)
代码:
#include <cstdio> #include <cmath> #include <cctype> #include <algorithm> using namespace std; const int N=65536; const int M=200010; const int D=1000001; int n,a ; int m,unit; struct Q { int l,r,id; friend inline int operator < (Q qa,Q qb) { return qa.l/unit!=qb.l/unit?qa.l/unit<qb.l/unit:qa.r<qb.r; } }q[M]; inline int Read(void) { int x=0; char c=getchar(); for (;!isdigit(c);c=getchar()); for (;isdigit(c);c=getchar()) x=x*10+c-'0'; return x; } int vis[D],cnt; int ans[M]; int main(void) { n=Read(); for (int i=1;i<=n;i++) a[i]=Read(); m=Read(); for (int i=1;i<=m;i++) q[i].l=Read(),q[i].r=Read(),q[i].id=i; unit=(int)sqrt(n); sort(q+1,q+m+1); int l=1,r=0; for (int i=1;i<=m;i++) { for (;l>q[i].l;l--) { vis[a[l-1]]++; if (vis[a[l-1]]==1) cnt++; } for (;l<q[i].l;l++) { vis[a[l]]--; if (!vis[a[l]]) cnt--; } for (;r<q[i].r;r++) { vis[a[r+1]]++; if (vis[a[r+1]]==1) cnt++; } for (;r>q[i].r;r--) { vis[a[r]]--; if (!vis[a[r]]) cnt--; } ans[q[i].id]=cnt; } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
2. 另一个离线算法
其实还可以想一想其他的离线算法。按照左端点排序行不行?尝试一下吧。
排完序后,固定左端点l=1l=1,可以在O(n)O(n)求出此时[1,r][1,r]的不同数的个数f[i]f[i]。
接下来,我们考虑ll变成l+1l+1对∀r∈[l+1,n]\forall r\in[l+1,n]的影响。
①若[l+1,r][l+1,r]中不存在ala_l,那么f[r]−−f[r]--;
②若[l+1,r][l+1,r]中存在ala_l,那么f[r]f[r]不变。
我们只要找到下一个与ala_l相同的坐标nextlnext_l,那么将区间[l+1,nextl−1][l+1,next_l-1]中的所有f[i]f[i]减11即可。
区间更改,单点求值,考虑使用区间数据结构。
树状数组就可以了,应该会跑得很快的。
这个算法不知道网上有没有,自己想出来的233333。
时间复杂度:O(nlogn)O(n\log n)
代码:实测916ms916ms
#include <cstdio> #include <cctype> #include <algorithm> using namespace std; const int N=65536; const int M=200010; const int D=1000001; int n; int a ; int tag[D]; int nxt ; int m; struct Ques { int l,r,id; friend inline int operator < (Ques qa,Ques qb) { return qa.l<qb.l; } }q[M]; int ans[M]; inline int read(void) { int x=0; char c=getchar(); for (;!isdigit(c);c=getchar()); for (;isdigit(c);c=getchar()) x=x*10+c-'0'; return x; } int cnt ; struct TreeArray { int t ; inline int lowbit(int i) { return i&-i; } inline void ins(int i,int add) { for (;i<=n;i+=lowbit(i)) t[i]+=add; } inline int query(int i) { int cnt=0; for (;i;i-=lowbit(i)) cnt+=t[i]; return cnt; } }tr; int main(void) { n=read(); for (int i=1;i<=n;i++) a[i]=read(); for (int i=1;i<=n;i++) tag[a[i]]=n+1; for (int i=n;i>=1;i--) nxt[i]=tag[a[i]],tag[a[i]]=i; m=read(); for (int i=1;i<=m;i++) q[i].id=i,q[i].l=read(),q[i].r=read(); sort(q+1,q+m+1); for (int i=1;i<=n;i++) tag[a[i]]=0; for (int i=1;i<=n;i++) { cnt[i]=cnt[i-1]; if (!tag[a[i]]) cnt[i]++; tag[a[i]]=1; } int now=1; for (int i=1;i<=m;i++) { for (;now<q[i].l;now++) tr.ins(now+1,-1),tr.ins(nxt[now],1); ans[q[i].id]=tr.query(q[i].r)+cnt[q[i].r]; } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
3. 可持久化数据结构的在线解法
不要从离线算法的排序入手,考虑从问题的解决入手。区间[l,r][l,r]的不同数个数,就是每个在区间中最后一次出现的数的个数,也就是在[l,r][l,r]中满足next[i]>rnext[i]>r的个数,即[1,r][1,r]中满足next[i]>rnext[i]>r的个数减去[1,l−1][1,l-1]中满足next[i]>rnext[i]>r的个数。
以权值为点,坐标为链建立可持久化线段树在线解决问题!!!
代码:实测1956ms1956ms
#include <cstdio> #include <cctype> const int N=65536; const int S=2097152; const int D=1048576; int n; int a ; inline int Read(void) { int x=0; char c=getchar(); for (;!isdigit(c);c=getchar()); for (;isdigit(c);c=getchar()) x=x*10+c-'0'; return x; } int pre ,tmp[D]; struct Sgt { struct Node { int lc,rc; int l,r; int cnt; }tr[S]; int tot; int Build(int l,int r) { int now=++tot; tr[now].l=l; tr[now].r=r; if (l!=r) { int mid=l+r>>1; tr[now].lc=Build(l,mid); tr[now].rc=Build(mid+1,r); } return now; } int Ins(int vtx,int w) { int now=++tot; tr[now].l=tr[vtx].l; tr[now].r=tr[vtx].r; tr[now].cnt=tr[vtx].cnt+1; if (tr[now].l!=tr[now].r) { int mid=tr[now].l+tr[now].r>>1; if (w<=mid) { tr[now].lc=Ins(tr[vtx].lc,w); tr[now].rc=tr[vtx].rc; } else { tr[now].lc=tr[vtx].lc; tr[now].rc=Ins(tr[vtx].rc,w); } } return now; } int Query(int vtx,int w) { if (tr[vtx].l==tr[vtx].r) return tr[vtx].cnt; int mid=tr[vtx].l+tr[vtx].r>>1; return w<=mid?Query(tr[vtx].lc,w):tr[tr[vtx].lc].cnt+Query(tr[vtx].rc,w); } }sgt; int root ; void Init(void) { n=Read(); for (int i=1;i<=n;i++) a[i]=Read(); for (int i=1;i<=n;i++) { pre[i]=tmp[a[i]]; tmp[a[i]]=i; } root[0]=sgt.Build(0,n); for (int i=1;i<=n;i++) root[i]=sgt.Ins(root[i-1],pre[i]); } int m; int x,y; void Work(void) { m=Read(); for (int i=1;i<=m;i++) { x=Read(),y=Read(); printf("%d\n",sgt.Query(root[y],x-1)-(x-1)); } } int main(void) { Init(); Work(); return 0; }
跑得更慢了啊……
继续继续,一定还可以优化!
4. 改进算法三——又一个离线算法
求在[l,r][l,r]中满足next[i]>rnext[i]>r的个数,区间减法不仅可以用[1,r]−[1,l−1][1,r]-[1,l-1],还可以用[l,n]−[r+1,n][l,n]-[r+1,n],又注意到[r+1,n][r+1,n]的个数就是(n−r)(n-r)个,那么只用求出[l,n][l,n]中next[i]>rnext[i]>r的个数即可。普遍的想法是把询问排序,然后通过ll的移动和权值线段树或者树状数组的更新完成询问。
先贴两个代码:
代码1:线段树(1164ms1164ms)
#include <cstdio> #include <cctype> #include <algorithm> using namespace std; const int N=65536; const int S=2097152; const int D=1048576; const int M=S; int n,m; int a ; struct Q { int l,r; int id; friend inline int operator < (Q qa,Q qb) { return qa.r<qb.r; } }q[M]; int pre ,tmp[D]; inline int Read(void) { int x=0; char c=getchar(); for (;!isdigit(c);c=getchar()); for (;isdigit(c);c=getchar()) x=x*10+c-'0'; return x; } void Init(void) { n=Read(); for (int i=1;i<=n;i++) a[i]=Read(); for (int i=1;i<=n;i++) { pre[i]=tmp[a[i]]; tmp[a[i]]=i; } m=Read(); for (int i=1;i<=m;i++) { q[i].l=Read(); q[i].r=Read(); q[i].id=i; } sort(q+1,q+m+1); } struct Sgt { struct Node { int l,r; int cnt; }tr[S]; void Build(int now,int l,int r) { tr[now].l=l; tr[now].r=r; if (l!=r) { int mid=l+r>>1; Build(now<<1,l,mid); Build(now<<1|1,mid+1,r); } } void Ins(int now,int w) { tr[now].cnt++; if (tr[now].l==tr[now].r) return; int mid=tr[now].l+tr[now].r>>1; if (w<=mid) Ins(now<<1,w); else Ins(now<<1|1,w); } int Query(int now,int w) { if (tr[now].l==tr[now].r) return tr[now].cnt; int mid=tr[now].l+tr[now].r>>1; return w<=mid?Query(now<<1,w):tr[now<<1].cnt+Query(now<<1|1,w); } }sgt; int now; int ans[M]; void Work(void) { sgt.Build(1,0,n-1); for (int i=1;i<=n;i++) { sgt.Ins(1,pre[i]); for (;now<m&&q[now+1].r==i;now++) ans[q[now+1].id]=sgt.Query(1,q[now+1].l-1)-(q[now+1].l-1); } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); } int main(void) { Init(); Work(); return 0; }
代码2:树状数组(实测876ms876ms)
#include <cstdio> #include <cctype> #include <algorithm> using namespace std; const int N=65536; const int S=2097152; const int D=1048576; const int M=S; int n,m; int a ; struct Q { int l,r; int id; friend inline int operator < (Q qa,Q qb) { return qa.r<qb.r; } }q[M]; int pre ,tmp[D]; inline int Read(void) { int x=0; char c=getchar(); for (;!isdigit(c);c=getchar()); for (;isdigit(c);c=getchar()) x=x*10+c-'0'; return x; } void Init(void) { n=Read(); for (int i=1;i<=n;i++) a[i]=Read(); for (int i=1;i<=n;i++) { pre[i]=tmp[a[i]]; tmp[a[i]]=i; } m=Read(); for (int i=1;i<=m;i++) { q[i].l=Read(); q[i].r=Read(); q[i].id=i; } sort(q+1,q+m+1); } struct TreeArray { int tr ; inline int lowbit(int i) { return i&-i; } void Ins(int i,int add) { for (i++;i<=n;i+=lowbit(i)) tr[i]+=add; } int Query(int i) { int sum=0; for (i++;i;i-=lowbit(i)) sum+=tr[i]; return sum; } }tr; int nowq; int ans[M]; void Work(void) { for (int i=1;i<=n;i++) { tr.Ins(pre[i],1); for (;nowq<m&&q[nowq+1].r==i;nowq++) ans[q[nowq+1].id]=tr.Query(q[nowq+1].l-1)-(q[nowq+1].l-1); } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); } int main(void) { Init(); Work(); return 0; }
然而,我们可以继续优化!
优化的方法就是想办法把O(mlogm)O(m\log m)的排序给弄快一些。
排序已经做到最快了,我们能不能不排序?
当然可以,像保存图一样开个链表就行了……
最后一个代码:实测832ms832ms
#include <cstdio> #include <cctype> #include <algorithm> using namespace std; const int N=65536; const int M=200010; const int D=1000001; int n; int a ; int tag[D]; int nxt ; int m; struct G { int r,nxt; }mp[M]; int tt,hd ; int ans[M]; inline int read(void) { int x=0; char c=getchar(); for (;!isdigit(c);c=getchar()); for (;isdigit(c);c=getchar()) x=x*10+c-'0'; return x; } inline int ins(int l,int r) { mp[++tt].r=r; mp[tt].nxt=hd[l]; hd[l]=tt; } int cnt ; struct TreeArray { int t ; inline int lowbit(int i) { return i&-i; } inline void ins(int i,int add) { for (;i<=n;i+=lowbit(i)) t[i]+=add; } inline int query(int i) { int cnt=0; for (;i;i-=lowbit(i)) cnt+=t[i]; return cnt; } }tr; int main(void) { n=read(); for (int i=1;i<=n;i++) a[i]=read(); for (int i=1;i<=n;i++) tag[a[i]]=n+1; for (int i=n;i>=1;i--) nxt[i]=tag[a[i]],tag[a[i]]=i; int l,r; m=read(); for (int i=1;i<=m;i++) l=read(),r=read(),ins(l,r); for (int i=n;i>=1;i--) { tr.ins(nxt[i]-1,1); for (int k=hd[i];k;k=mp[k].nxt) ans[k]=((n-i+1)-tr.query(mp[k].r-1))-(n-mp[k].r); } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
Sumarize
①区间问题可以从离线、在线的角度入手,也可以从问题的求解入手。②区间问题离线的三种处理方法,不一定要排序,可以使用图来解决。
相关文章推荐
- HDU2412 Party at Hali-Bula(树形DP)
- RocEDU.阅读.写作
- java学习之网络编程之echo程序
- 查找目录中同名的文件或者文件夹
- 每周学一点Egret(10) 插件分享
- 简述strcpy、sprintf与memcpy的区别
- checkbox选中的问题(Ajax.BeginForm)
- 血的教训:数据丢失灾难恢复总结
- 虚函数
- Nim Game
- java学习之线程池的实现
- 获取一个文件$view,一个参数数组$param,将数组中的key对应的值-->赋值于$view文件中的变量
- 年会感想
- iOS 中 frame、center 和bounds 的深入了解
- C#基础精华----枚举
- C#基础精华----枚举
- poj3660Cow Contest(Floyd闭包)
- Android 保存应用的图片到系统相册中
- Java多线程梳理
- java学习之生产者和消费者案例