题解:P4348 [CERC2015] Cow Confinement
hongyuecheng
·
·
题解
P4348 [CERC2015] Cow Confinement 题解
引言
这是一篇超详细的题解。
题意
给定一个长和宽为 1 \times {10}^{6} 的矩形网格,告诉我们牛、花、栅栏的位置。求对于单独每头牛在只向下或向右的情况下,不跨过栅栏,能到达的花的个数。(栅栏不相交)
解题思路
整体上我们从下向上一行一行处理。
我们考虑一朵花对与其同一行的位置的贡献。我们发现位于第 j 列的花,记 x_i 为最大的小于 j 的栅栏或左边界位置,会使 [x_i,j] 区间的答案加一,如下图所示。
对于第 i 行,如果没有下栅栏阻挡,我们发现可以由第 i-1 行转移而来,如图蓝色箭头。
如果有栅栏阻挡,就无法从第 i-1 行转移,
答案清零,如红色箭头。
其实还有一种情况。当走到栅栏的尽头,我们要拆除左右栅栏,这时答案又可以从右边转移来了。
以下图为例,区间 [5,8] 是由下面一层转移而来。此时右侧栅栏已经走到了尽头,先拆除 2 号格子和 5 号格子左侧的栅栏,由于区间 [2,4] 无法从下面一层转移过来,所以要先清空区间 [2,4],再从点 (5,1) 向左转移至下一个栅栏边界或左边界。
但是,我们发现计算到左上角时,右下角会被重复计算。(如图所示)
所以我们存一下右下角的答案,在左上角减去就行了。
代码实现
从解题思路中不难看出,我们需要一种高效的数据结构来维护答案,要支持区间加、区间赋值为零和单点查询。于是我想到了线段树!线段树区间赋值为零就是对一个区间乘上 0。 线段树2
我们先把点存下来,进行排序(我用的是优先队列),先按纵轴从大到小排,对于同一排,先处理栅栏,再处理花,最后处理牛,其中栅栏从左往右排。
这么排的原因:
-
栅栏会影响到花对答案的修改,所以先栅栏。
-
花会影响到最终答案,所以把花排在牛的前面。
-
左边栅栏边界拆除会影响右边栅栏拆除时赋值的区间,所以先左后右。
对于点:
struct point{
ll x,y;
ll type;
bool operator <(const point p)const{
if(y!=p.y) return y<p.y;//从左往右排
if(type<=0&&p.type>0) return 1;//先栅栏
if(type>0&&p.type<=0) return 0;
if(type<0&&p.type==0) return 1;//把花排在牛的前面
if(type==0&&p.type<0) return 0;
return x>p.x;
}
}a[1000007];
ll cnt;
priority_queue<point> pq;
void add_p(ll x,ll y,ll type){
cnt++;
a[cnt].y=y;
a[cnt].x=x;
a[cnt].type=type;
}
$type_i=0$ 代表这个点是花。
$type_i>0$ 代表这个点是栅栏,第 $k$ 号栅栏其左上点 $type_i=k\times 2-1$,其右下点 $type_j=k\times 2$。
对于栅栏和牛:
为了便于快速查找最大的小于 $k$ 的栅栏位置,和删除位置为 $j$ 的栅栏。这里用一个 set 来存左右栅栏边界(为了统一我都存栅栏在左侧的格子)。由于栅栏不相邻,不用考虑被去重。
```cpp
struct fence{
ll w;//栅栏宽度
ll val;//右下角的值
}fc[200007];
set<ll> left_fc;
ll cow[200007];//存每个牛的答案
```
把所有点加到大根堆里,每次拿堆顶进行处理。
```cpp
for(int i=1;i<=cnt;i++){
pq.push(a[i]);
}
rgh++;//防止越界
init();
while(!pq.empty()){
point p=pq.top();
pq.pop();
```
拿出的点如果是栅栏右下点,先记录右下角的答案(方便左上角减去),由于被栅栏遮挡的地方无法从下一层转移,所以用线段树对区间乘上一个 $0$。最后,用 set 记录左右栅栏。
```cpp
if(!(p.type%2)){
ll id=p.type/2;
ll w=fc[id].w,x=p.x;
fc[id].val=(x+1>rgh?0LL:qry(x+1,1,1,rgh));//防止越界
update(x-w+1,x,1,1,rgh,0,0);
left_fc.insert(x+1);
left_fc.insert(x-w+1);
}
```
拿出的点如果是栅栏左上点,我们先拆除左右栅栏边界,
二分找到最大的小于等于当前位置的栅栏边界或左边界 $k$,$[k,x+w-1]$ 都加上 $x+w$ 上的答案。
```cpp
else{
ll id=p.type/2+1;
ll w=fc[id].w,val=fc[id].val,x=p.x;
left_fc.erase(x);
left_fc.erase(x+w);
auto it=left_fc.upper_bound(x);
if(it==left_fc.begin()){
update(x,x+w-1,1,1,rgh,0,0);
update(1,x+w-1,1,1,rgh,1,qry(x+w,1,1,rgh));
if(x!=1) update(1,x-1,1,1,rgh,1,-val);
}
else{
it--;
update(x,x+w-1,1,1,rgh,0,0);
update(*it,x+w-1,1,1,rgh,1,qry(x+w,1,1,rgh));
if(x!=1) update(*it,x-1,1,1,rgh,1,-val);
}
}
```
对于花和牛就比较简单了。如果是花,二分找到最大的小于等于当前位置的栅栏边界或左边界,对区间加 $1$。如果是牛,记录答案即可。
```cpp
else if(p.type==0){//花的情况
auto it=left_fc.upper_bound(p.x);
if(it==left_fc.begin()){
update(1,p.x,1,1,rgh,1,1);
}
else{
it--;
update(*it,p.x,1,1,rgh,1,1);
}
}
else{//牛的情况
cow[-p.type]=qry(p.x,1,1,rgh);
}
```
时间复杂度 $O(N\log (NR))$,其中 $N$ 为点的总数,$R$ 为最靠右节点的横坐标。
#### 完整代码
```cpp line-numbers
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f,m,n;
ll rgh;//右边界
struct point{
ll x,y;
ll type;
bool operator <(const point p)const{
if(y!=p.y) return y<p.y;
if(type<=0&&p.type>0) return 1;
if(type>0&&p.type<=0) return 0;
if(type<0&&p.type==0) return 1;
if(type==0&&p.type<0) return 0;
return x>p.x;
}
}a[1000007];
ll cnt;
void add_p(ll x,ll y,ll type){
cnt++;
a[cnt].y=y;
a[cnt].x=x;
a[cnt].type=type;
}
priority_queue<point> pq;
struct fence{
ll w;
ll val;
}fc[200007];
set<ll> left_fc;
ll cow[200007];
ll tr[5000006];
ll m_tag[5000006],a_tag[5000006];
void init(){
for(int i=1;i<=rgh;i++){
m_tag[i]=1;
}
}
inline ll ls(ll p){return p<<1;}
inline ll rs(ll p){return p<<1|1;}
void push_up(ll p){
tr[p]=tr[ls(p)]+tr[rs(p)];
return;
}
void func(ll p,ll m,ll a,ll l,ll r){//先乘后加
tr[p]*=m;
a_tag[p]*=m;
m_tag[p]*=m;
tr[p]+=a*(r-l+1);
a_tag[p]+=a;
return;
}
void push_down(ll p,ll l,ll r){
ll mid=(l+r)>>1;
func(ls(p),m_tag[p],a_tag[p],l,mid);
func(rs(p),m_tag[p],a_tag[p],mid+1,r);
m_tag[p]=1;
a_tag[p]=0;
return;
}
void update(ll ul,ll ur,ll p,ll l,ll r,ll m,ll a){
if(ul<=l&&ur>=r){
func(p,m,a,l,r);
return;
}
push_down(p,l,r);
ll mid=(l+r)>>1;
if(ul<=mid) update(ul,ur,ls(p),l,mid,m,a);
if(ur>mid) update(ul,ur,rs(p),mid+1,r,m,a);
push_up(p);
return;
}
ll qry(ll x,ll p,ll l,ll r){
if(x==l&&x==r){
return tr[p];
}
push_down(p,l,r);
ll mid=(l+r)>>1,ans=0;
if(x<=mid) ans=qry(x,ls(p),l,mid);
else ans=qry(x,rs(p),mid+1,r);
push_up(p);
return ans;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>f;
for(int i=1;i<=f;i++){
ll x,y,xx,yy;
cin>>y>>x>>yy>>xx;
rgh=max(rgh,max(x,xx));
fc[i].w=xx-x+1;
add_p(x,y-1,i*2LL-1LL);//
add_p(xx,yy,i*2LL);
}
cin>>m;
for(int i=1;i<=m;i++){
ll x,y;
cin>>y>>x;
rgh=max(rgh,x);
add_p(x,y,0);
}
cin>>n;
for(int i=1;i<=n;i++){
ll x,y;
cin>>y>>x;
rgh=max(rgh,x);
add_p(x,y,-i);
}
for(int i=1;i<=cnt;i++){
pq.push(a[i]);
}
rgh++;//防止越界
init();
while(!pq.empty()){
point p=pq.top();
pq.pop();
if(p.type>0){
if(!(p.type%2)){
ll id=p.type/2;
ll w=fc[id].w,x=p.x;
fc[id].val=(x+1>rgh?0LL:qry(x+1,1,1,rgh));
update(x-w+1,x,1,1,rgh,0,0);
left_fc.insert(x+1);
left_fc.insert(x-w+1);
}
else{
ll id=p.type/2+1;
ll w=fc[id].w,val=fc[id].val,x=p.x;
left_fc.erase(x);
left_fc.erase(x+w);
auto it=left_fc.upper_bound(x);
if(it==left_fc.begin()){
update(x,x+w-1,1,1,rgh,0,0);
update(1,x+w-1,1,1,rgh,1,qry(x+w,1,1,rgh));
if(x!=1) update(1,x-1,1,1,rgh,1,-val);
}
else{
it--;
update(x,x+w-1,1,1,rgh,0,0);
update(*it,x+w-1,1,1,rgh,1,qry(x+w,1,1,rgh));
if(x!=1) update(*it,x-1,1,1,rgh,1,-val);
}
}
}
else if(p.type==0){
auto it=left_fc.upper_bound(p.x);
if(it==left_fc.begin()){
update(1,p.x,1,1,rgh,1,1);
}
else{
it--;
update(*it,p.x,1,1,rgh,1,1);
}
}
else{
cow[-p.type]=qry(p.x,1,1,rgh);
}
}
for(int i=1;i<=n;i++){
cout<<cow[i]<<'\n';
}
return 0;
}
```