网址:http://codeforces.com/problemset/problem/1190/D
题意:
给出平面直角坐标系下的点,使用一个上不封顶的矩形把某些点围住作为一个集合,两个集合不同只要数量不同或者数量相同时存在一个点不同,集合必须非空,求这样的集合的数量。
题解:
扫描线法:对$y$轴自顶向下,对于其中一层$y_i$,有以下$n$个点,坐标是$x_1,x_2,x_3......x_n$,以递增序排列,在某一时候,$x_i$加入序列,$y_i$层之前已经计数的矩形不受影响,增加的是加入了新的点之后可构成的新矩形。所以以这个点作为新点,统计加入前的矩形。为了保证有边界,左边界取$1$,有边界取$x$轴的最大值$+1$。则由计数原理,不同的矩形只要存在一条边界不同即可,点数$=$边数$-1$,故矩形数$=$这个点(左边点数$+1)$$*$$($右边点数$+1)$。由于同一个横坐标的值的点等价,对于纵坐标更小而横坐标相同的点,因为已经计算它上面的点,所以其不能和纵坐标大的点累加,故扫描时直接覆盖而不是累加。
然后是技巧,这个题的的数据范围是$2e5$,所以需要数据结构优化,然后坐标值范围是$1e9$,所以需要离散化。
AC代码:
(线段树)
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#pragma GCC optimize(2)
using namespace std;
struct Point
{
int x,y;
bool operator<(const Point &a)const
{
return y==a.y?x<a.x:y>a.y;
}
};
Point p[200005];
int x[200005],y[2000005];
struct Segtree
{
struct node
{
int l,r;
int v;
}tr[200005*4];
void build(int l,int r,int k)
{
tr[k].l=l,tr[k].r=r;
if(l==r)
{
tr[k].v=0;
return;
}
int m=(l+r)/2;
build(l,m,k*2);
build(m+1,r,k*2+1);
tr[k].v=tr[k*2].v+tr[k*2+1].v;
}
void update(int pos,int k,int val)//单点修改
{
if(tr[k].l==tr[k].r)
{
tr[k].v=val;
return;
}
int m=(tr[k].l+tr[k].r)/2;
if(pos<=m)
update(pos,k*2,val);
else if(pos>m)
update(pos,k*2+1,val);
tr[k].v=tr[k*2].v+tr[k*2+1].v;
}
int query(int l,int r,int k)
{
if(l>r)
return 0;
if(l<=tr[k].l&&r>=tr[k].r)
{
return tr[k].v;
}
int m=(tr[k].l+tr[k].r)/2;
//cout<<tr[k].l<<" "<<tr[k].r<<endl;
int ans=0;
if(l<=m)
ans+=query(l,r,k*2);
if(r>m)
ans+=query(l,r,k*2+1);
return ans;
}
};
Segtree seg;
int ydiv[200005];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin>>n;
for(int i=0;i<n;++i)
{
cin>>x[i]>>y[i];
p[i].x=x[i];
p[i].y=y[i];
}
sort(x,x+n);
sort(y,y+n);
int xnew=unique(x,x+n)-x;
int ynew=unique(y,y+n)-y;
for(int i=0;i<n;++i)
{
p[i].x=lower_bound(x,x+xnew,p[i].x)-x+1;
p[i].y=lower_bound(y,y+ynew,p[i].y)-y+1;
//cout<<p[i].x<<" "<<p[i].y<<endl;
}
sort(p,p+n);
//扫描线
seg.build(1,xnew+1,1);
int beg=0,cur=0;
long long sum=0;
while(beg<n)
{
int pos=0;
while(cur<n&&p[cur].y==p[beg].y)
{
seg.update(p[cur].x,1,1);
ydiv[pos++]=p[cur++].x;
}
ydiv[pos]=xnew+1;
for(int i=0;i<pos;++i)
{
long long cntl=seg.query(1,ydiv[i]-1,1);
long long cntr=seg.query(ydiv[i]+1,ydiv[i+1]-1,1);
sum+=(cntl+1)*(cntr+1);
}
beg=cur;
}
cout<<sum<<endl;
return 0;
}
(树状数组)
注:单点修改时,因为每个横坐标值最多只会同时有一个点(被覆盖),所以已经修改成$1$的地方,再需要修改时,也是覆盖成1,所以直接用一个$vis$数组记录是否已经修改。
#include <iostream>
#include <algorithm>
#include <cstring>
#pragma GCC optimize(2)
using namespace std;
struct Fenwick
{
bool cntx[200005];
int c[200005];
int lowbit(int x)
{
return x&(-x);
}
void update(int pos,int val,int n)//Containing error
{
if(cntx[pos])
return;
else
{
cntx[pos]=1;
for(int i=pos;i<=n;i+=lowbit(i))
c[i]+=val;
}
}
int sum(int pos,int n)
{
int sum=0;
for(int i=pos;i;i-=lowbit(i))
sum+=c[i];
return sum;
}
int query(int l,int r,int n)
{
if(l>r)
return 0;
return sum(r,n)-sum(l-1,n);
}
void print(int n)
{
for(int i=0;i<n;++i)
cout<<c[i]<<" ";
cout<<endl;
}
};
struct Point
{
int x,y;
bool operator<(const Point &a)const
{
return y==a.y?x<a.x:y>a.y;
}
};
Point p[200005];
int x[200005],y[200005],ydiv[200005];
Fenwick tr;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin>>n;
for(int i=0;i<n;++i)
{
cin>>x[i]>>y[i];
p[i].x=x[i];
p[i].y=y[i];
}
sort(x,x+n);
sort(y,y+n);
int xn=unique(x,x+n)-x;
int yn=unique(y,y+n)-y;
for(int i=0;i<n;++i)
{
p[i].x=lower_bound(x,x+xn,p[i].x)-x+1;
p[i].y=lower_bound(y,y+yn,p[i].y)-y+1;
//cout<<p[i].x<<" "<<p[i].y<<endl;
}
sort(p,p+n);
//扫描线
memset(tr.c,0,sizeof(tr.c));
int beg=0,cur=0;
//max xn+1
long long sum=0;
while(beg<n)
{
int pos=0;
while(cur<n&&p[cur].y==p[beg].y)
{
tr.update(p[cur].x,1,xn+1);
ydiv[pos++]=p[cur++].x;
//tr.print(xn+1);
}
ydiv[pos]=xn+1;
for(int i=0;i<pos;++i)
{
long long cntl=tr.query(1,ydiv[i]-1,xn+1);
long long cntr=tr.query(ydiv[i]+1,ydiv[i+1]-1,xn+1);
sum+=(cntl+1)*(cntr+1);
}
beg=cur;
}
cout<<sum<<endl;
return 0;
}