P3997 [SHOI2013] 扇形面积并
废话
感觉并没有紫题难度,比较简单。不过在拆分询问的时候有些麻烦。
题目传送门
题意过于简单,这里就不简化了。
题解
首先根据题意,我们先将题目中给定的图给拆分成 \(2 \times m\) 个区域

(借用了一下这篇 文章 的图片)。
然后我们可以将这拆分下来变成一个 \(1 \times 2m\) 的矩阵,红色矩阵覆盖 \([4,13]\),绿色矩阵覆盖 \([6,15]\)。
然后思考如何求解答案,类似与这类区间覆盖问题,我第一个想到的就是扫描线,我们将一个询问拆成插入和删除两个操作,在 \(a\) 处插入一个半径为 \(r\) 的扇形,在 \(b\) 处将其删除。然后依次枚举每个下标,先处理一下这里的操作,然后至少被 \(k\) 个扇形覆盖,我们则找到第 \(k\) 大的半径,它以内的体积都是可以被覆盖到的。为了求解第 \(k\) 大,我们可以使用线段树二分、平衡树等算法,这里我使用线段树二分,因为 \(r\) 只有 \(10^5\),不需要使用动态开点。
一些实现上的细节
首先就是 \(a,b\) 会小于 \(0\),需要加一个偏移量。
其次,题目并没有保证 \(a,b\) 的大小关系,所以这里需要分类讨论建边,如果 \(a>b\) 说明这个环绕了一圈,所以我们需要拆分成 \(4\) 个操作,具体的看代码,还有就是 \(a=b\) 的情况,那么这就是一个圆,也要特殊处理。
代码
#include<bits/stdc++.h>
#define int long long
#define ls id<<1
#define rs ls|1
using namespace std;
const int N=1000005,inf=100000;
int n,m,k;
int t[N<<2];
vector<int>q[N<<1];
int Get(int x){
return abs(x)/x;
}
void pushup(int id){
t[id]=t[ls]+t[rs];
return;
}
void update(int l,int r,int id,int x,int y){
if(l==r){t[id]+=y;return;}
int mid=(l+r)>>1;
if(x<=mid)update(l,mid,ls,x,y);
if(mid<x)update(mid+1,r,rs,x,y);
pushup(id);
return;
}
int query(int l,int r,int id,int rnk){
if(l==r)return l;
int mid=(l+r)>>1;
if(rnk<=t[ls])return query(l,mid,ls,rnk);
return query(mid+1,r,rs,rnk-t[ls]);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
int r,a,b;cin>>r>>a>>b;
a+=m;b+=m;
if(a<b){q[a].push_back(r);q[b].push_back(-r);}
else if(a==b){q[0].push_back(r);q[2*m].push_back(-r);}
else{
q[0].push_back(r);q[b].push_back(-r);
q[a].push_back(r);q[2*m].push_back(-r);
}
}
int cnt=0;
int ans=0;
for(int i=0;i<=2*m;i++){
for(auto j:q[i]){
cnt+=Get(j);
update(1,inf,1,abs(j),Get(j));
}
if(cnt<k)continue;
int s=query(1,inf,1,cnt-k+1);
ans+=s*s;
}
cout<<ans<<"\n";
return 0;
}

浙公网安备 33010602011771号