第十一届蓝桥杯B组省赛平面切分题解
题目分析
欧拉定理 封闭几何体的面数=边数-顶点数+2可以考虑数学归纳法证明。
若对于画了若干直线的平面:
1、顶点就是直线的交点
2、边就是链接顶点的线段
3、面就是除了顶点与边剩下的东西
把无穷远处的直线端点捏到一起形成的集合体的面数与原图面数相同。
将刚才制造的点减去后本题使用:
面数=边数-顶点数+1求解
只说思路,细节见代码注释
顶点
两条直线相交解方程组就好。
边数
一条直线上会被n个不同的点会被分成n+1段,所以一个顶点加一条边。
去重
要考虑超过两条直线交与一点的情况以及输入中有重复直线的情况
精度
由于浮点误差会给顶点判重造成影响,所以建议使用分数(用1/3带替0.333…)
分数类需要自行实现。
代码
尽管代码实现的挺长,但注释得很明白,上面没说的细节都有。
#include<bits\stdc++.h>
#define N 1005
using namespace std;
int gcd(int a1,int b1){//最大公因数,用于约分
int a=min(a1,b1);
int b=max(a1,b1);
int temp;
while(temp=b%a){
b=a;
a=temp;
}
return a;
}
class frac{//分数类fraction,消除浮点误差
public:
int nume;//分子numerator
int deno;//分母denominator
frac()=default;//构造方法们
frac(int n){
nume=n;
deno=1;
}
frac(int n,int d){
nume=n;
deno=d;
reduce();
}
void reduce(){//约分
if(nume==0){
deno=1;
return;
}
int sign=(nume*deno>0)?1:-1;
nume=abs(nume);
deno=abs(deno);
int temp=gcd(nume,deno);
nume/=temp;
deno/=temp;
nume*=sign;
}
frac operator+(const frac f){//重载必要的运算符
return frac(nume*f.deno+deno*f.nume,deno*f.deno);
}
frac operator-(const frac f){
return frac(nume*f.deno-deno*f.nume,deno*f.deno);
}
frac operator*(const frac f){
return frac (nume*f.nume,deno*f.deno);
}
frac operator/(const frac f){
return frac(nume*f.deno,deno*f.nume);
}
bool operator<(const frac &f)const{
return nume*f.deno<f.nume*deno;
}
bool operator==(const frac &f)const{
return nume==f.nume&&deno==f.deno;
}
};
class point{//点类,可用于存放1、直线参数1、直线交点,给set用方便去重。
public:
frac x;
frac y;
point()=default;
point(frac x,frac y){
this->x=x;
this->y=y;
}
bool operator<(const point& p)const{
return x<p.x||x==p.x&&y<p.y;
}
};
//打印分数与点,便于调试
ostream & operator<<(ostream &out,frac f){
if(f.deno!=1)
out <<f.nume<<'/'<<f.deno;
else
out <<f.nume;
return out;
}
ostream & operator<<(ostream &out,point p){
out <<'('<<p.x<<','<<p.y<<')';
return out;
}
int num;//直线数量
int a[N],b[N];//用于保存截距
point tp;//保存直线的交点
bool hasAns(int i,int j){//求出直线的交点
if(a[i]!=a[j]){//直线的k不同才有解
//原理A(x1,x2)=(a1,a2)=>(x1,x2)=inv(A)(a1,a2)
//其中A为系数矩阵(x1,x2)为解向量
//inv为矩阵求逆=伴随矩阵/行列式
frac a22(a[i]),a12(1),a13(-b[i]);//求伴随矩阵
frac a21(-a[j]),a11(-1),a23(-b[j]);
frac det=a11*a22-a12*a21;//求行列式
//cout << a11 <<' '<< a12 <<' '<< a13 << endl;//打印用于调试
//cout << a21 <<' '<< a22 <<' '<< a23 << endl;
//cout << det << endl;
tp.x=(a11*a13+a12*a23)/det;//获取方程的解
tp.y=(a21*a13+a22*a23)/det;
return true;
}
return false;
}
void getLines(){//获取输入
set<point> lset;//直线集合用于去重
cin >> num;
for(int i=0;i<num;++i){
cin >> a[0] >> b[0];
if(lset.find({a[0],b[0]})==lset.end()){//只有元集合中没有才加入
lset.insert({a[0],b[0]});
}
}
num=0;
for(auto i=lset.begin();i!=lset.end();++i,++num){
a[num]=(*i).x.nume;//每个点的坐标都是分数,因为输入为整数,分子略
b[num]=(*i).y.nume;
}
}
int main(){
int plane,apex,side;//面、顶点、边 欧拉公式,对于开放平面p=1+s-a
set<point> taset,aset;//临时顶点集(对每条线),顶点集(对全部)
getLines();
side=num;
for(int i=0;i<num;++i){//计算点和边
taset.clear();//每条直线最初都没有被分段
for(int j=0;j<num;++j){
if(hasAns(i,j)){//如果有解
if(aset.find(tp)==aset.end()){//统计点
aset.insert(tp);
}
if(taset.find(tp)==taset.end()){//统计当前直线上的点
taset.insert(tp);
}
}
}
side+=taset.size();//统计边,几个不重点就加几
}
apex=aset.size();//获得顶点数(交点数)
/*for(auto i=pset.begin();i!=pset.end();++i){//打印解又来调试
cout << *i << endl;
}*/
plane=1+side-apex;//计算面数
cout << plane << endl;//打印最终答案
return 0;
}