题解:P3997 [SHOI2013] 扇形面积并

· · 题解

卡常过了!!!

思路:

有点类似差分哈。

首先我们先把这一个被平均分成 2m 份扇形的圆转换成 2m 棵线段树,但是,空间肯定会超

所以我们可以每一份单独处理

先准备一个长度为 2m,类型为 pair<int,int>二维动态数组 v,先让扇形的起始位置 a 和结束位置 bm 使它们非负,然后再像下面这样:

        v[a+m].push_back({r,1});
        v[b+m].push_back({r,-1});

第一行表示从 a+m 的位置开始放入一个半径为 r 的扇形,同时他可以造成价值为 1 的厚度贡献;

第二行表示在 b+m 的位置撤销 a+m 处放入的半径为 r 的扇形的厚度贡献;

但是由于输入的特殊性,可能会出现 a>b 的情况,此时只需在 0 的位置放入一个半径为 r 的扇形即可。

然后我们遍历这 2m 份扇形,对于第 i 个扇形 v_i 中的第 j 个元素的半径 v[i][j].first 和贡献 v[i][j].second

我们使用线段树进行区间加的操作,加的范围就是 [1,v[i][j].first],加的贡献就是 v[i][j].second

然后对于这份扇形,我们去二分找出最后一个大于等于 k 的位置,设这个位置为 R

那这份扇形的面积的贡献就为:

由于最后要乘上 $\frac{2m}{π}$,化简后即为 $R^2$。 接着,就有以下代码: ```cpp #include<bits/stdc++.h> #define int long long #define up(id) tree[id].sum=tree[id*2].sum+tree[id*2+1].sum using namespace std; struct node { int l,r,sum,lazy; }tree[8000005]; int n,m,k,ans; vector<pair<int,int>>v[2000005]; void build(int id,int l,int r) { tree[id].l=l; tree[id].r=r; if(l==r)return; int mid=l+r>>1; build(id*2,l,mid); build(id*2+1,mid+1,r); } void down(int id) { tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy; tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy; tree[id*2].lazy+=tree[id].lazy; tree[id*2+1].lazy+=tree[id].lazy; tree[id].lazy=0; } void update(int id,int l,int r,int sum) { if(tree[id].l>r||tree[id].r<l)return; if(tree[id].l>=l&&tree[id].r<=r) { tree[id].lazy+=sum; tree[id].sum+=(tree[id].r-tree[id].l+1)*sum; return; } down(id); update(id*2,l,r,sum); update(id*2+1,l,r,sum); up(id); } int query(int id,int l,int r) { if(tree[id].l>r||tree[id].r<l)return 0; if(tree[id].l>=l&&tree[id].r<=r)return tree[id].sum; down(id); return query(id*2,l,r)+query(id*2+1,l,r); } signed main() { cin>>n>>m>>k; build(1,1,2*m); for(int i=0,r,a,b;i<n;i++) { cin>>r>>a>>b; v[a+m].push_back({r,1}); v[b+m].push_back({r,-1}); if(b<a)v[0].push_back({r,1}); } for(int i=0;i<2*m;i++) { for(auto j:v[i]) { update(1,1,j.first,j.second); } int l=1,r=100000,mid,res=0; while(l<=r) { mid=l+r>>1; if(query(1,mid,mid)>=k) { l=mid+1; res=mid; } else r=mid-1; } ans+=res*res; } cout<<ans; return 0; } ``` 但是,你这样写会 TLE 一个点。 为了减少常数,先把一部分 `long long` 类型的数据改为 `int` 类型,再加上**快读快写**, 是的没错,仍然会 TLE,因为**普通线段树常数太大**了。 所以,这边给到一个 **zkw 线段树**,zkw 线段树的**常数、空间**要比普通线段树**少**。 至于 zkw 线段树,请参考[这篇](https://www.luogu.com.cn/article/zyplvygx)文章。 时间复杂度为 $O(m\log_2^2n)$。 # AC 代码: ```cpp #include<bits/stdc++.h> #define il inline #define re register using namespace std; il int read() { re int x=0,f=1; char c; while(!isdigit(c=getchar())) { if(c=='-')f=-1; } while(isdigit(c)) { x=(x<<1)+(x<<3)+(c^48); c=getchar(); } return x*f; } struct node { int sum,lazy; }tree[8000005]; int n=read(),m=read(),k=read(),P=1; long long ans; vector<pair<int,int>>v[2000005]; il void update(int l,int r,int v) { long long ln=0,rn=0,nn=1; for(l=P+l-1,r=P+r+1;l^r^1;l/=2,r/=2,nn*=2) { tree[l].sum+=v*ln; tree[r].sum+=v*rn; if(~l&1) { tree[l^1].lazy+=v; tree[l^1].sum+=v*nn; ln+=nn; } if(r&1) { tree[r^1].lazy+=v; tree[r^1].sum+=v*nn; rn+=nn; } } for(;l;l/=2,r/=2) { tree[l].sum+=v*ln; tree[r].sum+=v*rn; } } il long long query(int l,int r) { long long ln=0,rn=0,nn=1,sum=0; for(l=P+l-1,r=P+r+1;l^r^1;l/=2,r/=2,nn*=2) { if(tree[l].lazy)sum+=tree[l].lazy*ln; if(tree[r].lazy)sum+=tree[r].lazy*rn; if(~l&1) { sum+=tree[l^1].sum; ln+=nn; } if(r&1) { sum+=tree[r^1].sum; rn+=nn; } } for(;l;l/=2,r/=2) { sum+=tree[l].lazy*ln; sum+=tree[r].lazy*rn; } return sum; } signed main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); for(;P<=100001;P*=2); for(int i=0,r,a,b;i<n;i++) { r=read(); a=read(); b=read(); v[a+m].push_back({r,1}); v[b+m].push_back({r,-1}); if(b<a)v[0].push_back({r,1}); } for(int i=0;i<2*m;i++) { int l=1,r=100000,mid; long long res=0; for(auto j:v[i]) { update(1,j.first,j.second); } while(l<=r) { mid=l+r>>1; if(query(mid,mid)>=k) { l=mid+1; res=mid; } else r=mid-1; } ans+=res*res; } cout<<ans; return 0; } ```