12.8日记
扫描线
- P5490:矩形面积并。
思路:看了一天才勉强看懂。首先离散化,线段树上每个节点表示一段区间。每次修改矩形的扫描线时,可以证明一定可以将其拆分成logn个区间,所以复杂度是对的。cnt记录这个区间被覆盖了几次。len记录这个区间至少被覆盖了一次的长度。这样每次加面积就是\(O(1)\)的。记得不用下推标记&&数组开大点。
#include<bits/stdc++.h> using namespace std; #define mid (l+r)/2 #define LL long long const int M=4e5+20; LL lsh[M*2]; unordered_map<double,int> rev; struct Line{ int l,r,h,d; Line(int a=0,int b=0,int c=0,int dd=0):l(a),r(b),h(c),d(dd){} bool operator<(const Line &x)const { return h<x.h; } }; vector<Line> line; struct Tree{ int cnt;//被完全覆盖的次数 LL len;//区间长度 Tree(int a=0,double b=0):cnt(a),len(b){} }v[4*M]; inline void pushup(int id,int l,int r){ if (v[id].cnt) v[id].len=lsh[r+1]-lsh[l]; else v[id].len=v[id*2].len+v[id*2+1].len; } void build(int id,int l,int r){ v[id].cnt=v[id].len=0; if (l==r) return; build(id*2,l,mid); build(id*2+1,mid+1,r); } void operate(int id,int l,int r,int ql,int qr,int x){ if (ql<=l&&r<=qr){ v[id].cnt+=x; pushup(id,l,r); return; } if (ql<=mid) operate(id*2,l,mid,ql,qr,x); if (mid<qr) operate(id*2+1,mid+1,r,ql,qr,x); pushup(id,l,r); } int main(){ int n; while(~scanf("%d",&n)){ line.clear(),rev.clear(); for(int i=1;i<=n;++i){ int a,b,c,d; scanf("%d%d%d%d",&a,&b,&c,&d); line.push_back(Line(a,c,b,1)),line.push_back(Line(a,c,d,-1)), lsh[2*i-1]=a,lsh[2*i]=c; } sort(line.begin(),line.end()); sort(lsh+1,lsh+2*n+1); int len=unique(lsh+1,lsh+2*n+1)-(lsh+1)-1; for(int i=1;i<=len+1;++i) rev[lsh[i]]=i; build(1,1,len); LL ans=0; for(int i=1;i<n*2;++i){ operate(1,1,len,rev[line[i-1].l],rev[line[i-1].r]-1,line[i-1].d); ans+=v[1].len*(line[i].h-line[i-1].h); } printf("%lld\n",ans); } return 0; }
- HDU1542:实数矩形面积并
和上一题一模一样。
#include<bits/stdc++.h> using namespace std; #define mid (l+r)/2 const int M=4e5+20; double lsh[M*2]; unordered_map<double,int> rev; struct Line{ double l,r,h; int d; Line(double a=0,double b=0,double c=0,int dd=0):l(a),r(b),h(c),d(dd){} bool operator<(const Line &x)const { return h<x.h; } }; vector<Line> line; struct Tree{ int cnt;//被完全覆盖的次数 double len;//区间长度 Tree(int a=0,double b=0):cnt(a),len(b){} }v[4*M]; inline void pushup(int id,int l,int r){ if (v[id].cnt) v[id].len=lsh[r+1]-lsh[l]; else v[id].len=v[id*2].len+v[id*2+1].len; } void build(int id,int l,int r){ v[id].cnt=v[id].len=0; if (l==r) return; build(id*2,l,mid); build(id*2+1,mid+1,r); } void operate(int id,int l,int r,int ql,int qr,int x){ if (ql<=l&&r<=qr){ v[id].cnt+=x; pushup(id,l,r); return; } if (ql<=mid) operate(id*2,l,mid,ql,qr,x); if (mid<qr) operate(id*2+1,mid+1,r,ql,qr,x); pushup(id,l,r); } int main(){ int n,z=0; while(~scanf("%d",&n)&&n){ ++z; line.clear(),rev.clear(); for(int i=1;i<=n;++i){ double a,b,c,d; scanf("%lf%lf%lf%lf",&a,&b,&c,&d); line.push_back(Line(a,c,b,1)),line.push_back(Line(a,c,d,-1)), lsh[2*i-1]=a,lsh[2*i]=c; } sort(line.begin(),line.end()); sort(lsh+1,lsh+2*n+1); int len=unique(lsh+1,lsh+2*n+1)-(lsh+1)-1; for(int i=1;i<=len+1;++i) rev[lsh[i]]=i; build(1,1,len); double ans=0; for(int i=1;i<n*2;++i){ operate(1,1,len,rev[line[i-1].l],rev[line[i-1].r]-1,line[i-1].d); ans+=v[1].len*(line[i].h-line[i-1].h); } printf("Test case #%d\nTotal explored area: %.2f\n\n",z,ans); } return 0; }
- HDU1255:至少覆盖了两次的矩形面积并
思路:比较妙,主要还是要利用区间和的性质。len1表示当前区间至少被覆盖了1次的长度,len2表示当前区间至少被覆盖了2次的长度。那么len1和之前的处理一样,len2要分三种情况。cnt=2就是整个区间,cnt=1就是左右的len1和,cnt=0就是左右的len2和。注意要对叶子节点单独处理!!!前两道题就忘了要搞这个!!!
#include<bits/stdc++.h> using namespace std; #define mid (l+r)/2 const int M=4e5+20; double lsh[M*2]; unordered_map<double,int> rev; struct Line{ double l,r,h; int d; Line(double a=0,double b=0,double c=0,int dd=0):l(a),r(b),h(c),d(dd){} bool operator<(const Line &x)const { return h<x.h; } }; vector<Line> line; struct Tree{ int cnt;//被完全覆盖的次数 double len;//区间长度 Tree(int a=0,double b=0):cnt(a),len(b){} }v[4*M]; inline void pushup(int id,int l,int r){ if (v[id].cnt) v[id].len=lsh[r+1]-lsh[l]; else v[id].len=v[id*2].len+v[id*2+1].len; } void build(int id,int l,int r){ v[id].cnt=v[id].len=0; if (l==r) return; build(id*2,l,mid); build(id*2+1,mid+1,r); } void operate(int id,int l,int r,int ql,int qr,int x){ if (ql<=l&&r<=qr){ v[id].cnt+=x; pushup(id,l,r); return; } if (ql<=mid) operate(id*2,l,mid,ql,qr,x); if (mid<qr) operate(id*2+1,mid+1,r,ql,qr,x); pushup(id,l,r); } int main(){ int n,z=0; while(~scanf("%d",&n)&&n){ ++z; line.clear(),rev.clear(); for(int i=1;i<=n;++i){ double a,b,c,d; scanf("%lf%lf%lf%lf",&a,&b,&c,&d); line.push_back(Line(a,c,b,1)),line.push_back(Line(a,c,d,-1)), lsh[2*i-1]=a,lsh[2*i]=c; } sort(line.begin(),line.end()); sort(lsh+1,lsh+2*n+1); int len=unique(lsh+1,lsh+2*n+1)-(lsh+1)-1; for(int i=1;i<=len+1;++i) rev[lsh[i]]=i; build(1,1,len); double ans=0; for(int i=1;i<n*2;++i){ operate(1,1,len,rev[line[i-1].l],rev[line[i-1].r]-1,line[i-1].d); ans+=v[1].len*(line[i].h-line[i-1].h); } printf("Test case #%d\nTotal explored area: %.2f\n\n",z,ans); } return 0; }
主席树
- P3834:给一个序列,询问区间第k小。
构造:主席树,叶子节点v[i]=j表示数i出现了j次,维护区间和,节点表示对应范围的数的个数。单点加减,单路查询。
#include<bits/stdc++.h> using namespace std; #define mid ((l+r)/2) const int M=8e6+10,Mm=2e5+20; int cnt; int a[Mm],b[Mm],root[Mm],L[M],R[M],v[M]; unordered_map<int,int> rev; int build(int l,int r){//建空树 int rt=++cnt; v[rt]=0; if (l==r) return rt; L[rt]=build(l,mid); R[rt]=build(mid+1,r); return rt; } int update(int idp,int l,int r,int pos,int x){ int rt=++cnt; L[rt]=L[idp],R[rt]=R[idp],v[rt]=v[idp]+x; if (l==r) return rt; if (pos<=mid) L[rt]=update(L[idp],l,mid,pos,x); else R[rt]=update(R[idp],mid+1,r,pos,x); return rt; } int query(int lid,int nid,int l,int r,int k){ int Lnum=v[L[nid]]-v[L[lid]];//当前区间左子树数的个数 if (l==r) return l; if (Lnum>=k) return query(L[lid],L[nid],l,mid,k); else return query(R[lid],R[nid],mid+1,r,k-Lnum); } int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=a[i]; sort(b+1,b+n+1); int len=unique(b+1,b+n+1)-(b+1); for(int i=1;i<=len;++i) rev[b[i]]=i; root[0]=build(1,len); for(int i=1;i<=n;++i) root[i]=update(root[i-1],1,len,rev[a[i]],1); for(int i=1;i<=m;++i){ int c1,c2,c3; scanf("%d%d%d",&c1,&c2,&c3); printf("%d\n",b[query(root[c1-1],root[c2],1,len,c3)]); } return 0; }
- P3567:给定一序列,询问某一区间是否有出现>区间长度一半次的数,有则输出该数,没有则返回0。
构造方式与上题相同,查询函数有所变化,这次比较的是固定长度len。
#include<bits/stdc++.h> using namespace std; #define mid (l+r)/2 const int M=1.6e7+10,Mm=5e5+20; int cnt,v[M],L[M],R[M],root[Mm],a[Mm],b[Mm]; int build(int l,int r){ int rt=++cnt; v[rt]=0; if (l==r) return rt; build(l,mid),build(mid+1,r); return rt; } int operate(int idp,int l,int r,int pos,int x){ int rt=++cnt; L[rt]=L[idp],R[rt]=R[idp],v[rt]=v[idp]+x; if (l==r) return rt; if (pos<=mid) L[rt]=operate(L[idp],l,mid,pos,x); else R[rt]=operate(R[idp],mid+1,r,pos,x); return rt; } int query(int lid,int nid,int l,int r,int len){ if (l==r) return l; if (v[L[nid]]-v[L[lid]]>len/2) return query(L[lid],L[nid],l,mid,len); if (v[R[nid]]-v[R[lid]]>len/2) return query(R[lid],R[nid],mid+1,r,len); return 0; } unordered_map<int,int> rev; int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=a[i]; sort(b+1,b+n+1); int len=unique(b+1,b+n+1)-(b+1); for(int i=1;i<=len;++i) rev[b[i]]=i; root[0]=build(1,len); for(int i=1;i<=n;++i) root[i]=operate(root[i-1],1,len,rev[a[i]],1); for(int i=1;i<=m;++i){ int ca,cb; scanf("%d%d",&ca,&cb); printf("%d\n",b[query(root[ca-1],root[cb],1,len,cb-ca+1)]); } return 0; }
明日计划
- 扫描线三维:
- 主席树:P1801(对顶堆), P2633
- 可删堆:
- CDQ代替树状数组。