计算几何 val.3
自适应辛普森法
可以用来求多边形的面积并(圆也行)
定积分
定积分的几何意义是函数的曲线上 \(x\) 的一段区间与 \(x\) 轴围成的曲边梯形的带符号面积
表示法为
\[ \int_{a}^{b} f(x) \mathrm{d} x \]
引入
计算方法:
分成一堆小区间
\[ \int_{a}^{b} f(x) \mathrm{d} x=\lim _{n \rightarrow \infty} \sum_{i=1}^{n} \frac{b-a}{n} f\left(a+\frac{b-a}{n} i\right) \]牛顿-莱布尼茨公式
如果
\[ F^{\prime}(x)=f(x) \]
则
\[ \int_{a}^{b} f(x) \mathrm{d} x=F(b)-F(a) \]
这个可以求:\(\int_a^b(\frac 1 x)dx = \ln |b|-\ln |a|\)这也是连接定积分和不定积分的桥梁
对于一些难求的积分,我们可以用数值积分来求,其中常用的是自适应辛普森积分
辛普森公式
此公式用二次函数来拟合原函数
\[ \int_{a}^{b} f(x) \mathrm{d} x \approx \int_{a}^{b}\left(A x^{2}+B x+C\right) \mathrm{d} x \]
\[ =\frac{A}{3}\left(b^{3}-a^{3}\right)+\frac{B}{2}\left(b^{2}-a^{2}\right)+C(b-a) \]
\[ =\frac{2 A(b-a)\left(b^{2}+a b+a^{2}\right)+3 B(b+a)(b-a)+6 C(b-a)}{6} \]
提出\(b-a\),
\[ =\frac{(b-a)\left(2 A b^{2}+2 A a b+2 A a^{2}+3 B b+3 B a+6 C\right)}{6} \]
\[ =\frac{(b-a)\left(A a^{2}+B a+C+A b^{2}+B b+C+A a^{2}+2 A a b+A b^{2}+2 B b+2 B a+4 C\right)}{6} \]
\[ =\frac{(b-a)\left(f(a)+f(b)+A(a+b)^{2}+2 B(a+b)+4 C\right)}{6} \]
\[ =\frac{(b-a)\left(f(a)+f(b)+4\left(A\left(\frac{a+b}{2}\right)^{2}+B\left(\frac{a+b}{2}\right)+C\right)\right)}{6} \]
\[ =\frac{(b-a)\left(f(a)+f(b)+4 f\left(\frac{a+b}{2}\right)\right)}{6} \]
于是可以得到公式:
\[ \int_{a}^{b} f(x) \mathrm{d} x \approx \frac{(b-a)\left(f(a)+f(b)+4 f\left(\frac{a+b}{2}\right)\right)}{6} \]
当然,对于二次函数这是对的
对于其余情况,\(b-a\)越小,上面两个式子越接近
这种情况下我们就要调整精度
处理精度
考虑把一段长的区间分成很多段小区间求和
可是分的太少了不能满足精度要求,太多了会TLE
那么考虑什么时候停止分下去呢?
对于当前区间,求出\(ans=simpson(l,r),mid=\frac{l+r}{2}\)
然后求出对于下一层区间的答案:\(ls=simpson(l,mid),rs=simpson(mid,r)\)
注意此处mid右边不用加一,不是整数域
如果\(|ls+rs-ans|<eps\),即满足精度要求,可以停止二分
考虑到一些小的误差加起来很大,eps要设的比题目要求的小一点
而且下一层的eps是上一层的二分之一,因为有两个
代码实现
double F(...){ ... } double simpson(double l,double r){ double mid=(l+r)/2.0; return (r-l)/6.0*(F(l)+4.0*F(mid)+F(r)); } double solve(double l,double r,double ans,double eps){ double mid=(l+r)/2.0; double ls=simp(l,mid),rs=simp(mid,r); if(fabs(ls+rs-ans)<eps*15) return ls+rs+(ls+rs-ans)/15; else return solve(l,mid,ls,eps*0.5)+solve(mid,r,rs,eps*0.5); }
等一下,好像实现和上面的思路不同?
if(fabs(ls+rs-ans)<eps*15) return ls+rs+(ls+rs-ans)/15;
这\(15\)是个啥东西?
噔 噔 咚
论证,请(绝望)
最后移一下项就好了,得到ls+rs+(ls+rs-ans)/15
模板
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define db double using namespace std; db a,b,c,d,l,r; db F(db x){ return (c*x+d)/(a*x+b); } db simp(db l,db r){ db mid=(l+r)/2.0; return (r-l)/6.0*(F(l)+4.0*F(mid)+F(r)); } db solve(db l,db r,db ans,db eps){ db mid=(l+r)/2.0; db ls=simp(l,mid),rs=simp(mid,r); if(fabs(ls+rs-ans)<eps*15) return ls+rs+(ls+rs-ans)/15; else return solve(l,mid,ls,eps*0.5)+solve(mid,r,rs,eps*0.5); } int main(){ scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&l,&r); printf("%.6f",solve(l,r,simp(l,r),1e-8)); return 0; }
时间复杂度
精度不能开太小,开要求精度再多2~3位都很稳
练习
找不到题啊。。
面积并:
\[ S=\int_l^rf(x)dx \]
\(f(x)\)为一条垂直于x轴的线的覆盖的长度
然后就可以用辛普森积分算了
算\(f\)的话可以求出所有交点,按上点排序,O(n)枚举计算出下一条线是否和当前有交点,并计算长度
90分代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; #define db double int n; const int N = 1001; db x[N],y[N],r[N]; const double eps=1e-3; struct node{ db u,d; }p[N]; int tp; int cmp(const node &aa,const node &bb){ return aa.u<bb.u; } db F(db pos){ tp=0; for(int i=1;i<=n;i++){ if(pos>=x[i]-r[i]&&pos<=x[i]+r[i]){ p[++tp].u=y[i]-sqrt(r[i]*r[i]-(x[i]-pos)*(x[i]-pos)); p[tp].d=y[i]+sqrt(r[i]*r[i]-(x[i]-pos)*(x[i]-pos)); } } sort(p+1,p+tp+1,cmp); db nu=p[1].u,nd=p[1].d,ans=0; for(int i=2;i<=tp;i++){ if(p[i].u<=nd){ nd=max(nd,p[i].d); }else{ ans+=(nd-nu); nu=p[i].u,nd=p[i].d; } } ans+=(nd-nu); return ans; } db simp(db l,db r){ db mid=(l+r)*0.5; return (r-l)/6.0*(F(l)+4.0*F(mid)+F(r)); } db solve(db l,db r,db ans,db eps){ db mid=(l+r)*0.5; db ls=simp(l,mid),rs=simp(mid,r); if(fabs(ls+rs-ans)<15.0*eps) return ls+rs+(ls+rs-ans)/15.0; else return solve(l,mid,ls,eps*0.5)+solve(mid,r,rs,eps*0.5); } int main(){ scanf("%d",&n); db ml=1926081700.1,mr=-1926081700.1; for(int i=1;i<=n;i++){ scanf("%lf%lf%lf",&x[i],&y[i],&r[i]); ml=min(x[i]-r[i],ml); mr=max(mr,x[i]+r[i]); } printf("%.3f",solve(ml,mr,simp(ml,mr),eps)); return 0; }
最后一个点被卡了,认识到此算法只能用来骗分。艹
闵可夫斯基和
空间中点集的和
有一些性质,比如,凸包之间的闵可夫斯基和一定是凸包
求凸包之间的闵可夫斯基和的方法:把两个凸包的每一条向量都抠出来,按照极角序排序构成新凸包
实现方法:
pot P={-inf,-inf},Q={-inf,-inf},R={-inf,-inf}; n=read(); for(int i=1;i<=n;i++) { a[i].x=read();a[i].y=read(); if(dcmp(a[i].y-P.y)==0&&dcmp(a[i].x-P.x)<0)P=a[i]; if(dcmp(a[i].y-P.y)>0)P=a[i]; if(i!=1)f[++cnt]=a[i]-a[i-1];if(i==n)f[++cnt]=a[1]-a[i]; } n=read(); for(int i=1;i<=n;i++) { b[i].x=read();b[i].y=read(); if(dcmp(b[i].y-Q.y)==0&&dcmp(b[i].x-Q.x)<0)Q=b[i]; if(dcmp(b[i].y-Q.y)>0)Q=b[i]; if(i!=1)f[++cnt]=b[i]-b[i-1];if(i==n)f[++cnt]=b[1]-b[i]; } n=read(); for(int i=1;i<=n;i++) { c[i].x=read();c[i].y=read(); if(dcmp(c[i].y-R.y)==0&&dcmp(c[i].x-R.x)<0)R=c[i]; if(dcmp(c[i].y-R.y)>0)R=c[i]; if(i!=1)f[++cnt]=c[i]-c[i-1];if(i==n)f[++cnt]=c[1]-c[i]; } sort(f+1,f+cnt+1,cmp); pot k=P+Q+R;p[++tot]=k; for(int i=1;i<=cnt;i++) { k=k+f[i]; if(i!=cnt&&dcmp(f[i].x*f[i+1].y-f[i].y*f[i+1].x)==0)continue; p[++tot]=k; } tot--;k=p[1];
没有例题,抱歉
Pick定理
结论
在一个平面直角坐标系内,以整点为顶点的简单多边形,设其内部整点数为\(a\),边上(包括顶点)的整点数为\(b\),则它的面积为\(a+\frac b 2 -1\)
证明:
例题
=模板
边上的格点数=|dx|和|dy|的最大公约数
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; int ol,x1,x2,x3,ya,yb,yc; int gcd(int x,int y) { return y==0?x:gcd(y,x%y); } int area() { return abs((x2-x1)*(yc-ya)-(x3-x1)*(yb-ya))/2; } int cal(int x1,int ya,int x2,int yb) { int dx,dy; if(x1<x2)dx=x2-x1; else dx=x1-x2; if(ya<yb)dy=yb-ya; else dy=ya-yb; return gcd(dx,dy); } int main() { while(scanf("%d%d%d%d%d%d",&x1,&ya,&x2,&yb,&x3,&yc)) { if(!x1&&!x2&&!x3&&!ya&&!yb&&!yc)break; ol=cal(x1,ya,x2,yb)+cal(x2,yb,x3,yc)+cal(x3,yc,x1,ya); printf("%d\n",area()-ol/2+1); } return 0; }
后记
其实 val.2 比 val.3 难且重要
但是不重要不代表不学呀
辛普森积分还是挺实用的,我觉得
没有val.4了,最多写写做题记录