【日记】12.8
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代替树状数组。