扫描线
扫描线直接照搬了...这篇主要是给之后的积分和凸包之类留的。
就是用线段树解决计算几何问题。
板梯
如何处理平面内一群矩形的面积交?
考虑用面积的朴素定义,\(S=ab\),相当于一堆面积合到一起就是 \(b\) 个可以不同的 \(a\) 相加。
我们维护坐标系的 \(x\) 值,再去跳 \(y\) 轴。具体地,只有一个矩形的下边界和上边界才会对当前 \(x\),也就是 \(a\) 值之和造成影响。我们记录一下矩形的上下底面,在下底面 \(y\) 值加上它覆盖的 \(x\) 段,在它的上底面减去它的 \(x\) 段,按照 \(y\) 给这些操作排序后去跳 \(y\),该加加改减减。线段树维护当前 \(y\) 内的 \(x\) 之和,相当于会形成很多的长度段,一堆叠在一起也算一个,瞎搞一下维护即可,\(S=\sum \Delta y*x_i\)。
放个代码方便复习。
#include<bits/stdc++.h>
#define MAXN 1000005
#define int long long
using namespace std;
int n;
struct node{int x1,x2,y;int opt;
}sq[MAXN<<1];
inline bool cmp(node a,node b){if(a.y==b.y)return a.opt>b.opt;return a.y<b.y;
}
int mp[MAXN<<2];
int ans,tmp;
struct Segment_Tree{#define ls(p) p<<1#define rs(p) p<<1|1struct TREE{int l,r;int val,tag;//维护一个当前x段的覆盖层数和贡献,只要有层数就有贡献,没层数就没贡献}tree[MAXN<<3];inline void build(int l,int r,int p){tree[p].l=l,tree[p].r=r,tree[p].val=tree[p].tag=0;if(l==r)return;int mid=l+r>>1;build(l,mid,ls(p));build(mid+1,r,rs(p));}inline void push_up(int p){//主要看看pushup即可if(tree[p].tag)tree[p].val=mp[tree[p].r+1]-mp[tree[p].l];else{tree[p].val=tree[ls(p)].val+tree[rs(p)].val;}}inline void modify(int l,int r,int k,int p){if(mp[tree[p].r+1]<=l||mp[tree[p].l]>=r)return;if(mp[tree[p].l]>=l&&mp[tree[p].r+1]<=r){tree[p].tag+=k;push_up(p);return;}// printf("nowmid=%lld,id=%lld,tree.l=%lld,tree.r=%lld\n",mid,p,tree[p].l,tree[p].r);modify(l,r,k,ls(p));modify(l,r,k,rs(p));push_up(p);}
}ST;
signed main(){scanf("%lld",&n);for(int i=1,x1,x2,y1,y2;i<=n;i++){scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);sq[i]=(node){x1,x2,y1,1};sq[i+n]=(node){x1,x2,y2,-1};mp[i]=x1,mp[i+n]=x2;}sort(mp+1,mp+1+n*2);tmp=unique(mp+1,mp+1+2*n)-mp-1;//printf("tmp=%lld\n",tmp);sort(sq+1,sq+1+n*2,cmp);ST.build(1,tmp-1,1);//printf("ced\n");for(int i=1;i<n*2;i++){// printf("solving %lld %lld %lldf\n",sq[i].x1,sq[i].x2,sq[i].opt);ST.modify(sq[i].x1,sq[i].x2,sq[i].opt,1);ans+=ST.tree[1].val*(sq[i+1].y-sq[i].y);}printf("%lld",ans);return 0;
}
计算周长
现在让你计算那坨矩形的周长。
同样的分析方法,矩形的下底面和上底面都会对当前答案做出贡献,但是贡献是 \(\Delta x\),相当于新增的或新减少 x 的才是当前 \(y\) 的 \(x\) 贡献,自己画个图想去。
然后考虑竖条对答案的贡献,当前线段树覆盖的一堆线段会形成许多个分开的块,具象下来就是当前 \(y\) 区段实际参与计算的矩形个数。
像是这张图虽然有三个矩形但只形成了两个块,因此实际的竖条个数是当前块数(2)乘以2。
用线段树再维护一下当前区段是否充满就可以推出来一共有几个块,满了就不加,不满就块数相加。
一道好玩的题
考虑用扫描线解决。
用矩形框点太蛋疼了,把点变成框那么大的矩形再用扫描线处理。
每个矩形相当于可以提供它权值的贡献,把这些贡献在 \(x\) 上叠加,发现线段树的极值就是当前 \(y\) 窗口能覆盖到的最大星星权值。没了。
自适应辛普森积分
挺简单的。
暴力展开一个二次函数的积分形式。
考虑这样一件事,对于一个要求积的函数 \(F(x)\),递归直到和这个二次函数拟合后停止后汇总即可求得一个大概的积分,这就是辛普森法,然而缺点是像保证正确性就会特别慢,想跑快点就要牺牲正确性。
所以提出了自适应辛普森积分,因为对于整个函数肯定有长得比较像二次函数的和长得不太像二次函数的。对于长得像二次函数的可以少递归几次保证复杂度,长得不像的就多递归几次保证正确性。所以分治地进行这个过程,因为二次函数劈两半还是二次函数,对于当前要处理的区间 \([l,r]\) 与把它当作二次函数得到的积分值 \(ans\),如果劈成两半 \([l,mid]\) 和 \([mid,r]\) 分开当二次函数求得的积分值之和 \(\sum\) 已经和 \(ans\) 十分接近了就说明这一段 \([l,r]\) 肯定是很像二次函数了,那就不递归了。反之,因为递归求解下去肯定不会丢失精度,重复上述过程直到拟合为止即可。
另外有一些正确性和复杂度上的优化。比如可以考虑钦定一个无论如何都要达到的递归层数 \(dep\) 来保证不丢精度,或者更改“十分接近”的定义即当前区间的 \(eps\) 来提速或者增加精度,还可以在递归之前扫一遍区间,直接跳过函数中可能存在的大片没有值的段来同时优化时间和正确性。
附上 板子 代码
#include<bits/stdc++.h>
#define db double
using namespace std;
db a,b,c,d,L,R;
const db eps=1e-7;
inline db f(db x){return (c*x+d)/(a*x+b);
}
inline db calc(db l,db r){db mid=(l+r)/2;return (r-l)*(f(l)+4*f(mid)+f(r))/6;
}
inline db asr(db l,db r,db eps,db ans){
// printf("nowlr=%.5f %.5f\n",l,r);db mid=(l+r)/2.0;db lv=calc(l,mid),rv=calc(mid,r);if(fabs(lv+rv-ans)<=15*eps)return lv+rv+(lv+rv-ans)/15;else return asr(l,mid,eps/2,lv)+asr(mid,r,eps/2,rv);
}
signed main(){scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&L,&R);printf("%.6f\n",asr(L,R,eps,calc(L,R)));return 0;
}
三角形面积并
考虑一种类似扫描线的思路,设 \(F(x_0)\) 为坐标系内 \(x=x_0\) 这条竖线所截的图形段长度,这个可以 \(O(n)\) 扫一遍解出来一堆 \((y_s,y_e)\) 然后排序贪心 \(O(nlogn)\) 求和求出,然后答案即为
圆的面积并
一样的,随便写写就过了。
月下柠檬树
一样的,随便写写就过了。
但是毕竟是道黑是吧所以还是稍微记一下。
脑玩一下发现影子拍上去圆面积不变只有 \(x\) 坐标变了,投影则是若干个圆和相邻圆的两个外公切线(如果有的话)的并形式。
仍然积分法求,对于当前递归到而要求的 \(f(x_0)\),先扫一遍包含那条线的圆能给的贡献(就是上一道题),然后看公切线怎么处理,草稿纸上划一下比如 \(\odot O_1\) 和 \(\odot O_2\) 圆心距为 \(x\) 则公切线所连半径和连心线夹角 \(\alpha\) 应有 \(cos\alpha=\frac{r_1-r_2}{x}\),正弦直接拿1减然后就能表示出公切点的坐标了,判一下有没有穿过 \(x_0\) 然后就是三角形面积并的做法,弄完贡献直接取max就行了。
(未开工)凸包
学到再说。