欧几里得几何求解——建模实践

欧几里得几何

目录

一,背景

二,需求

三,建模

1,数据层

2,事务层

3,对外依赖

4,调度层

四,编码

1,数据层

2,事务层

3,对外依赖

4,调度层

五,小结

1,事务层

2,对外依赖

3,核心领域知识

4,缺陷

5,代码中的彩蛋

六,破解关卡

1,1.2垂直平分线

2,3.8菱形

七,代码改进

1,修改play函数,搜索所有的解

2,剪枝

3,继续破解3.8菱形

4,6.6平移线段

八,分步判定

1,分步判定思路

2,数据结构扩充

3,分步判定实现

(1)判定集

(2)修改isGoodEntity函数,只需要id化即可

(3)分步判定

(4)调度(输入控制)

4,继续破解6.6平移线段

5,6.8平行四边形

6,6.9欧拉九点圆

九,继续优化

1,把静态局部变量改为全局变量

2,优化哈希方案——两级哈希

3,线段和直线

4,重新数据化

5,可视化——在线作图

6,优化第一步

7,优化最后一步

8,优化剪枝方案(待更新)

9,完整代码


 

一,背景

这是一个尺规作图的游戏,必须要把一章完美通过才能进入下一关,于是我卡在了第6章。

道具有3种:基本工具、复合工具、辅助工具

(1)基本工具:圆规和直线,1L1E,其中L是非辅助性道具的使用次数,E是基本工具的使用次数

(2)复合工具:垂直平分线工具、平行线工具等,是前面关卡通过尺规作图作出来之后解锁的工具,1LxE,x>1

(3)辅助工具:点工具、相交工具、手势工具,0L0E

注意:相交工具是取圆和直线的交点,点工具是在圆上或者直线上或者平面内任意地方取一点

每一关要想完美通过,有2个任务:L任务和E任务。

这个游戏的E任务比L任务难很多,但是E任务建模要比L任务简单,因为每个复合工具要体现成代码还是有不少工作量的,而且也会增大搜索算法求解问题的困难度。

 

二,需求

开发一个程序,用来求解欧几里得几何的E任务。

需求分析:

1,几乎所有关卡都有不需要点工具的解法,本程序只考虑不需要点工具的解法

(有趣的是,点工具可以说是所有工具里面最简单的,但是对于建模、对于求解来说却都是比较麻烦的)

当然,对于必须使用点工具的关卡,如果情况比较简单,在初始化局面的时候人为的添加点即可

2,初始局面由若干圆和直线组成,进行给定次数的基本工具的操作之后,变成满足关卡要求的最终局面

3,基本工具的操作的次数是给定的,一般在10以内,辅助工具任意用,复合工具不能用

4,每次操作有圆规和直线两种情况,如果看游戏提示的话,还能直接知道每次是圆规还是直线。每使用一次基本工具之后,默认充分使用相交工具,即把所有的相交点都取出来。

 

三,建模

1,数据层

这个游戏是通过基本工具和交点工具,把初始局面转化成最终满足条件的局面,所以这里有2个概念:局面、工具。

因为本游戏具有马尔科夫效应(无后效性),所以只需要静态局面信息即可,不需要存储历史操作记录。

局面是数据,由圆、直线、点组成,工具是方法

2,事务层

(1)基本工具:给定任意两点,画一个圆或直线

(2)相交工具:求出,新增的圆或直线,和已有的圆和直线,相交的所有点。

(3)更新局面,根据新增的圆或直线,和新增交点,刷新局面

3,对外依赖

对于不同的关卡,需要输入给程序的有3个信息:

(1)初始局面

(2)基本工具操作集,每次是圆还是直线

(3)终态局面判定:判定最终的局面是否满足游戏要求

4,调度层

(1)核心算法:搜索解空间,每一步枚举任意两点,调用工具接口和更新局面的接口

(2)顶层调度

 

四,编码

1,数据层

点的话很容易表示:

typedef struct Point {
    double x, y;
} Point;

直线采用一般式:ax + by + c = 0,表示一条直线需要3个数。 

圆的表示方法有很多,考虑到圆都是圆规做出来的,所以用标准式更方便计算

圆的标准式:(x-a)^2 + (y-b)^2 = c^2

typedef enum TYPE {
    CIRCLE,
    LINE,
    BUFF
}TYPE;

typedef struct Node {
    TYPE type;
    double a, b, c;
} Node;

最后,用圆、直线、点表示局面:

typedef struct Entity {
    vector<Node> vecNode;
    vector<Point> vecPoint;
} Entity;

为什么用vector,不用set呢?

因为要给判重做重载,相比给==符号做重载,我更习惯写独立接口

2,事务层

(1)基本工具:给定任意两点,画一个圆或直线

//两点距离平方和
double getDistanceSquar(Point a, Point b)
{
    return pow(a.x - b.x, 2) + pow(a.y - b.y, 2);
}
//两点距离
double getDistance(Point a, Point b)
{
    return sqrt(getDistanceSquar(a, b));
}
//画圆
Node makeCircle(Point center, Point a)
{
    return Node{ CIRCLE,center.x, center.y, getDistance(center, a) };
}
//画直线
Node makeLine(Point a, Point b)
{
    return Node{ LINE,b.y - a.y, a.x - b.x, a.y * b.x - a.x * b.y };
}
Node make(TYPE t, Point a, Point b)
{
    if (t == CIRCLE) {
        return makeCircle(a, b);
    }
    else {
        return makeLine(a, b);
    }
}

这一块我做了统一的接口,根据参数决定画圆还是画直线。

(2)相交工具:求出,新增的圆或直线,和已有的圆和直线,相交的所有点。

圆和圆、圆和直线的交点个数可能为0,1,2,直线和直线的交点个数可能为0,1

所以,这一块我做了统一的接口,返回2个交点,不够的都用常量invalidPoint来表示

直线和直线:

#define dps 0.0000001
Point invalidPoint= {0,12345};
bool sameValue(double x, double y)
{
    return abs(x - y) < dps;
}
// 直线的判定式
double deltaLineAndLine(Node s, Node t)
{
    return s.a * t.b - s.b * t.a;
}
// 两直线是否平行
bool isParallels(Node s, Node t)
{
    return sameValue(deltaLineAndLine(s, t), 0);
}
// 两直线交点
pair<Point, Point> lineAndLine(Node s, Node t)
{
    if (isParallels(s, t)) {
        return make_pair(invalidPoint, invalidPoint);
    }
    Point p = { (t.c * s.b - s.c * t.b) / deltaLineAndLine(s, t), (s.c * t.a - s.a * t.c) / deltaLineAndLine(s, t) };
    return make_pair(p, invalidPoint);
}

圆和直线:

// 一般式化为斜率式
void normallizeLine(Node &s)
{
    s.a /= -s.b, s.c /= -s.b, s.b /= -s.b;
}
// 圆和直线相交的判定式
double deltaCircleAndLine(Node s, Node t)
{
    //normallizeLine(t);
    return s.c * s.c * (t.a * t.a + 1) - pow(t.a * s.a - s.b + t.c, 2);
}
// 圆和线的交点数目,有0,1,2三种情况
int cicleAndLineNum(Node s, Node t)
{
    double delta = deltaCircleAndLine(s, t);
    if (delta < 0) {
        return 0;
    }
    if (delta == 0) {
        return 1;
    }
    return 2;
}
// 圆和线的交点
pair<Point, Point> cicleAndLine(Node s, Node t)
{
    normallizeLine(t);
    int ret = cicleAndLineNum(s, t);
    if (ret == 0) {
        return make_pair(invalidPoint, invalidPoint);
    }
    double sqrtDelta = sqrt(deltaCircleAndLine(s, t));
    Point p1, p2;
    p1.x = (s.a + t.a * s.b - t.a * t.c + sqrtDelta) / (t.a * t.a + 1), p1.y = p1.x * t.a + t.c;
    p2.x = (s.a + t.a * s.b - t.a * t.c - sqrtDelta) / (t.a * t.a + 1), p2.y = p2.x * t.a + t.c;
    return make_pair(p1, p2);
}

圆和圆:

// 圆的判定式
double deltaCircle(Node s)
{
    return s.a * s.a + s.b * s.b - s.c * s.c;
}
// 两圆相交的两点连成的直线,不需要判定两圆是否相交
Node cicleAndCicleToLine(Node s, Node t)
{
    return Node{ LINE, (t.a - s.a) * 2, (t.b - s.b) * 2, deltaCircle(s) - deltaCircle(t) };
}
//两圆交点
pair<Point, Point>cicleAndCicle(Node s, Node t)
{
    return cicleAndLine(s, cicleAndCicleToLine(s, t));
}

这里巧妙的利用了一个领域知识(平面几何知识)优化模型,消除重复知识

用两圆的标准式相减得到的直线,和任意一圆的交点个数,都等于两圆之间的交点个数

统一的接口:

//任意俩个Node相交
pair<Point, Point> getIntersectPoint(Node s, Node t)
{
    if (s.type == CIRCLE && t.type == CIRCLE) {
        return cicleAndCicle(s, t);
    }
    if (s.type == LINE && t.type == LINE) {
        return lineAndLine(s, t);
    }
    if (s.type == CIRCLE && t.type == LINE) {
        return cicleAndLine(s, t);
    }
    if (t.type == CIRCLE && s.type == LINE) {
        return cicleAndLine(t, s);
    }
    return make_pair(invalidPoint, invalidPoint);
}

(3)更新局面,根据新增的圆或直线,和新增交点,刷新局面

新增交点:

//两点重合
bool samePoint(Point a, Point b)
{
    return sameValue(a.x, b.x) && sameValue(a.y, b.y);
}
bool alreadyHavePoint(vector<Point>v, Point p)
{
    for (auto it = v.begin(); it != v.end(); it++) {
        if (samePoint(*it, p)) {
            return true;
        }
    }
    return false;
}
void addPoint(Entity& ent, Point p)
{
    if (!samePoint(p, invalidPoint) && !alreadyHavePoint(ent.vecPoint, p)) {
        ent.vecPoint.push_back(p);
    }
}
void addPoint(Entity& ent, pair<Point, Point>pai)
{
    addPoint(ent, pai.first);
    addPoint(ent, pai.second);
}
void addPoint(Entity& ent, Node s)
{
    for (auto it = ent.vecNode.begin(); it != ent.vecNode.end(); it++) {
        addPoint(ent, getIntersectPoint(*it, s));
    }
}

新增圆或直线:

bool sameCircle(Node s, Node t)
{
    return sameValue(s.a, t.a) && sameValue(s.b, t.b) && sameValue(s.c, t.c);
}
bool sameLine(Node s, Node t)
{
    normallizeLine(s);
    normallizeLine(t);
    return sameValue(s.a, t.a) && sameValue(s.c, t.c);
}
bool sameNode(Node s, Node t)
{
    if (s.type != t.type) {
        return false;
    }
    if (s.type == CIRCLE) {
        return sameCircle(s, t);
    }
    return sameLine(s, t);
}
bool alreadyHaveNode(Entity ent, Node s)
{
    for (auto it = ent.vecNode.begin(); it != ent.vecNode.end(); it++) {
        if (sameNode(*it, s)) {
            return true;
        }
    }
    return false;
}
bool addNode(Entity &ent, Node s)
{
    if (alreadyHaveNode(ent, s)) {
        return false;
    }
    addPoint(ent, s);
    ent.vecNode.push_back(s);
    return true;
}

3,对外依赖

对于不同的关卡,需要输入给程序的有3个信息:

(1)初始局面

(2)基本工具操作集,每次是圆还是直线

(3)终态局面判定:判定最终的局面是否满足游戏要求

经过领域专家(笔者客串)的分析,终态局面判定问题可以表述为,最终局面是否包括某些特定的圆、直线、点

这里我用了函数重载和模板编程,写出了优美的代码:

void input(TYPE &t)
{
    int x;
    cin>>x;
    t=TYPE(x);
}
void input(Node &s)
{
    input(s.type);
    cin>>s.a>>s.b>>s.c;
}
void input(Point &p)
{
    cin>>p.x>>p.y;
}

template<typename T>
void initVector(vector<T>&v)
{
    int len;
    cin>>len;
    v.resize(len);
    for(int i=0;i<len;i++){
        input(v[i]);
    }
}

Entity initEnt()
{
    Entity ent;
    initVector<Node>(ent.vecNode);
    initVector<Point>(ent.vecPoint);
    return ent;
}

vector<TYPE> initVecType()
{
    vector<TYPE>v;
    initVector<TYPE>(v);
    return v;
}

Entity goodEnt;
bool isGoodEntity(Entity ent)
{
    
    for (auto it = goodEnt.vecNode.begin(); it != goodEnt.vecNode.end(); it++) {
        if (!alreadyHaveNode(ent, *it)) {
            return false;
        }
    }
    for (auto it = goodEnt.vecPoint.begin(); it != goodEnt.vecPoint.end(); it++) {
        if (!alreadyHavePoint(ent.vecPoint, *it)) {
            return false;
        }
    }
    return true;
}

4,调度层

(1)核心算法:搜索解空间,每一步枚举任意两点,调用工具接口和更新局面的接口

Entity g_ent;
bool play(Entity ent, vector<TYPE> vt)
{
    Entity empty;
    if (vt.empty()) {
        g_ent = ent;
        return isGoodEntity(ent) ? true : false;
    }
    for (int i = 0; i < ent.vecPoint.size(); i++) {
        for (int j = 0; j < ent.vecPoint.size(); j++) {
            if (j == i)continue;
            Node node = make(vt[0], ent.vecPoint[i], ent.vecPoint[j]);
            Entity tmp = ent;
            TYPE temp = vt[0];
            if (!addNode(ent, node)) {
                ent = tmp;
                continue;
            }
            vt.erase(vt.begin());
            if (play(ent, vt)) {
                return true;
            }
            else {
                ent = tmp;
                vt.insert(vt.begin(),temp);
            }
        }
    }
    return false;
}

这只是一个极其简单的demo,要想正常工作,必须要做大量剪枝。

毫不夸张的说,剪枝才是算法的核心,但是我暂时没打算耗大量时间把这个算法调通,这一部分暂时略过,只备注一下简单的思路:

首先,去重是肯定需要而且核心的,先画直线A再画直线B,和先画直线B再画直线A是一样的,所以需要一个方法给局面做哈希映射

因为局面其实是由圆和直线确定的,所以键是不需要点的

map<vector<Node>, int> m;

很遗憾,这样也是不行的,因为直线的一般式:ax + by + c = 0,数值完全不同的2套a b c表示的也可能是一条直线,这块需要再优化一下。

其次,另外一个非常重要的剪枝思路就是,根据游戏的特性,在每次选取两点画圆或直线的时候,很多选择其实是可以直接排除掉的。这一块需要较深的领域知识。

最后,画直线的时候,先选点A再选点B,和先选点B再选点A是一样的,这也可以剪枝。

(2)顶层调度

void display(Node s)
{
    if(s.type==CIRCLE){
        printf("(x - %.3f)^2 + (y - %.3f)^2 = %.3f\n",s.a,s.b,s.c*s.c);
    }else{
        normallizeLine(s);
        printf("y = %.3fx + %.3f\n",s.a,s.c);
    }
}

void display(Entity ans)
{
    for(unsigned i=0;i<ans.vecNode.size();i++){
        display(ans.vecNode[i]);
    }
}
 
int main()
{
    Entity ent = initEnt();
    vector<TYPE> vt = initVecType();
    goodEnt = initEnt();
    if(play(ent, vt))display(g_ent);
    else cout<<"fail";
    return 0;
}

五,小结

1,事务层

事务层作为一个整体,对外其实只有2个接口:

画图make,更新局面addNode

2,对外依赖

需要根据关卡实际情况,需要输入的有3个信息:初始局面,操作集,终态判定

因为全部是vector,而且里面全部是double,所以采用了统一的输入格式,每个vector都是输入一个整数n,再输入n个实数,3个信息分别是2个、1个、2个vector

3,核心领域知识

需要核心领域知识的是:领域剪枝策略。

这个是最难的领域知识,有待思考,其他的简单的知识,已经在上述建模和编码的过程中用进去了。

4,缺陷

代码中有把直线一般式化成斜率式,所以开局建模的时候涉及到直线的,不能用水平或者竖直的直线来表示,这就稍微增加了复杂度。

5,代码中的彩蛋

输出的顺序,刚好就是所求的操作的顺序。

 

六,破解关卡

1,1.2垂直平分线

初始直线方程是 y = 1.000x + 0.000

目标直线方程是 y = -1.000x + -0.000

所以输入:

1
1 1 -1 0
2
1 1
-1 -1
3
0 0 1
1
1 1 1 0
0
运行得到

2,3.8菱形

   

如果输入:

1
1 2.414213562 -1 -0.923879532
2
0.38268343236 0
0 -0.923879532
7
0 0 1 0 1 1 1
3
1 2.414213562 -1 0.923879532
1 -2.414213562 -1 -0.923879532
1 -2.414213562 -1 0.923879532
0

其中2.414213562是cotan(22.5), 0.923879532是cos(22.5), 0.38268343236是sin(22.5)

那么需要的时间比较长。

但是因为是画菱形,而且最后三步都是画直线,所以我们可以猜想,最后三步就是菱形的最后三条线,于是我们只让程序计算前面5步即可。

输入:

1
1 2.414213562 -1 -0.923879532
2
0.38268343236 0
0 -0.923879532
5
0 0 1 0 1
1
1 -2.414213562 -1 -0.923879532
0

运行得到:

对应的图是:

图上标注了最后一条线,这条线确实是正确的,但是这样的局面,最后2步是无解的。

那么,我能不能把所有方案都输出出来,人工去挑选正确答案呢?

 

七,代码改进

1,修改play函数,搜索所有的解

void play(Entity ent, vector<TYPE> vt)
{
    if (vt.empty()) {
        if (isGoodEntity(ent)) {
            display(ent);
            cout << endl;
        }
        return;
    }
    for (int i = 0; i < ent.vecPoint.size(); i++) {
        for (int j = 0; j < ent.vecPoint.size(); j++) {
            if (j == i)continue;
            Node node = make(vt[0], ent.vecPoint[i], ent.vecPoint[j]);
            Entity tmp = ent;
            TYPE temp = vt[0];
            if (!addNode(ent, node)) {
                ent = tmp;
                continue;
            }
            vt.erase(vt.begin());
            play(ent, vt);
            ent = tmp;
            vt.insert(vt.begin(), temp);
        }
    }
}


int main()
{
    Entity ent = initEnt();
    vector<TYPE> vt = initVecType();
    goodEnt = initEnt();
    play(ent, vt);
    cout << "end";
    return 0;
}

结果,输出了大量相同的解。

2,剪枝

(1)直线避免画2次

    for (int i = 0; i < ent.vecPoint.size(); i++) {
        int startId = ((vt[0] == LINE) ? i : 0);  // 剪枝
        for (int j = startId; j < ent.vecPoint.size(); j++) {
            if (j == i)continue;

(2)相同的局面不重复计算

先把double映射到整数,再把Node的vector映射到set,只需要Node不需要Point,最后给set做个哈希记录即可

vector<int> normallizeNode(Node s)
{
    if (s.type == LINE) {
        normallizeLine(s);
    }
    vector<int>v(4);
    v[0] = s.type, v[1] = int(s.a * 1000), v[2] = int(s.b * 1000), v[3] = int(s.c * 1000);
    return v;
}

set<vector<int>>normallizeNode(vector<Node>vec)
{
    set<vector<int>>s;
    for(unsigned i=0;i<vec.size();i++)s.insert(normallizeNode(vec[i]));
    return s;
}

void play(Entity ent, vector<TYPE> vt)
{
    static map<set<vector<int>>, int>m;
    if (m[normallizeNode(ent.vecNode)])return; // 剪枝
    if (vt.empty()) {
        if (isGoodEntity(ent)) {
            display(ent);
            cout << endl;
        }
        m[normallizeNode(ent.vecNode)] = 1;
        return;
    }
    for (int i = 0; i < ent.vecPoint.size(); i++) {
        int startId = ((vt[0] == LINE) ? i : 0);  // 剪枝
        for (int j = startId; j < ent.vecPoint.size(); j++) {
            if (j == i)continue;
            Node node = make(vt[0], ent.vecPoint[i], ent.vecPoint[j]);
            Entity tmp = ent;
            TYPE temp = vt[0];
            if (!addNode(ent, node)) {
                ent = tmp;
                continue;
            }
            vt.erase(vt.begin());
            play(ent, vt);
            ent = tmp;
            vt.insert(vt.begin(), temp);
        }
    }
    m[normallizeNode(ent.vecNode)] = 1;
}

3,继续破解3.8菱形

输入不变,还是输入前5步,输出:

y = 2.414214x + -0.923880
(x - 0.383)^2 + (y - 0.000)^2 = 1.000
(x - 0.000)^2 + (y - -0.924)^2 = 1.000
y = -1.303225x + 0.498723
(x - -0.226)^2 + (y - 0.793)^2 = 3.000
y = -2.414214x + -0.923880

......此处省略其他11个答案

y = 2.414214x + -0.923880
(x - 0.000)^2 + (y - -0.924)^2 = 1.000
(x - -0.383)^2 + (y - -1.848)^2 = 1.000
y = -0.414214x + -1.465076
(x - -0.191)^2 + (y - -1.386)^2 = 0.250
y = -2.414214x + -0.923880

给的答案还是有点多,现在有2个思路:

(1)因为剪枝了,所以可以搜索更多的步数

(2)利用可视化工具,快速从答案列表中找出正在的答案

这2个思路可以同时开始尝试:

先运行程序,输入:

1
1 2.414213562 -1 -0.923879532
2
0.38268343236 0
0 -0.923879532
6
0 0 1 0 1 1
2
1 -2.414213562 -1 -0.923879532
1 -2.414213562 -1 0.923879532
0

在等待的过程中,思考如何做可视化。

然而,我突然又相到,只多加一层搜索,时间应该也就多十倍左右,不会太长,无论怎么样1分钟都能搜完。

但事实却是,搜了2分钟才有结果。

我突然又抓住了那一瞬间的灵感,此处需要剪枝!

在搜索了5步之后,如果需要的2条直线一条都没有出现,那么就不需要搜索第6步了。因为最后一步不可能产生2条直线。

好在2分钟还能勉强接受,可以算这一关达到要求了。

输出结果:

对应答案:

这样,就找到了正确的解法。

4,6.6平移线段

这是我在写本文时,无法过关的第一个关卡,虽然网上攻略一搜就有,但是咱程序员可是有自己的骄傲的!

   

运行程序,输入:

1
1 1 -1 0
3
0 0
1 1
5 0
6
0 0 0 0 1 0
0
1
6 1

其中,5 0这个点是随便取的,点和线段的位置并没有限制。

这里的终态判定就比较有意思了,我只写了6 1这个点,没有写直线的方程,但是几乎可以断定得到这个点的同时会得到这个线段

程序运行了7分钟,还在运行中,被我干掉了。

因为我想到了一种更好的判定方案:

1
1 1 -1 0
3
0 0
1 1
5 0
5
0 0 0 0 1
1
1 1 -1 -5
0

因为最后一步是画圆,所以第5步肯定就已经得到这条直线了。

输出:

(y - 1.000000x + 0.000000)
((x - 0.000)^2 + (y - 0.000)^2 - 2.000)
((x - 0.000)^2 + (y - 0.000)^2 - 25.000)
((x - 5.000)^2 + (y - 0.000)^2 - 25.000)
((x - 3.536)^2 + (y - 3.536)^2 - 25.000)
(y - 1.000000x + -5.000000)

......(此处省略非常非常非常多的疑似解)

(y - 1.000000x + 0.000000)
((x - 0.000)^2 + (y - 0.000)^2 - 25.000)
((x - 3.536)^2 + (y - 3.536)^2 - 14.645)
((x - -3.536)^2 + (y - -3.536)^2 - 25.000)
((x - -4.830)^2 + (y - 1.294)^2 - 98.296)
(y - 1.000000x + -5.000000)

于是又遇到了同样的问题,输出太多,要找出正确答案不容易。

所以,到了这,就不得不推出分步判定功能了。

 

八,分步判定

1,分步判定思路

把判定过程分为若干次判定,这其实也是一种剪枝策略。

2,数据结构扩充

之前的终态局面判定是一个判定,现在要多个判定,每个判定还要输入是第几步的判定。

以6.6平移线段关卡为例,第5步的判定为:

5
1
1 1 -1 -5
0

第6步的判定为:

6
0
1
6 1

前面的保持不变,所以输入合集是:

1
1 1 -1 0
3
0 0
1 1
5 0
6
0 0 0 0 1 0
5
1
1 1 -1 -5
0
6
0
1
6 1

3,分步判定实现

代码倒是不难改。

(1)判定集

vector<bool> endEnt;
vector<Entity> goodEnt;

endEnt用来记录哪一步需要判定,哪一步不需要判定。

(2)修改isGoodEntity函数,只需要id化即可

bool isGoodEntity(Entity ent,int id)
{
    for (auto it = goodEnt[id].vecNode.begin(); it != goodEnt[id].vecNode.end(); it++) {
        if (!alreadyHaveNode(ent, *it)) {
            return false;
        }
    }
    for (auto it = goodEnt[id].vecPoint.begin(); it != goodEnt[id].vecPoint.end(); it++) {
        if (!alreadyHavePoint(ent.vecPoint, *it)) {
            return false;
        }
    }
    return true;
}

(3)分步判定

void play(Entity ent, vector<TYPE> vt)
{
    static map<set<vector<int>>, int>m;
    if (m[normallizeNode(ent.vecNode)])return; // 剪枝
    if (vt.size()<endEnt.size() && endEnt[vt.size()]) {
        if (!isGoodEntity(ent,vt.size())) {
            return;
        }
    }
    if (vt.empty()) {
        display(ent);
        return;
    }
    for (int i = 0; i < ent.vecPoint.size(); i++) {
        int startId = ((vt[0] == LINE) ? i : 0);  // 剪枝
        for (int j = startId; j < ent.vecPoint.size(); j++) {
            if (j == i)continue;
            Node node = make(vt[0], ent.vecPoint[i], ent.vecPoint[j]);
            Entity tmp = ent;
            TYPE temp = vt[0];
            if (!addNode(ent, node)) {
                ent = tmp;
                continue;
            }
            vt.erase(vt.begin());
            play(ent, vt);
            ent = tmp;
            vt.insert(vt.begin(), temp);
        }
    }
    m[normallizeNode(ent.vecNode)] = 1;
}

(4)调度(输入控制)

int main()
{
    Entity ent = initEnt();
    vector<TYPE> vt = initVecType();
    endEnt.resize(vt.size());
    int id;
    while (cin >> id) {
        endEnt[vt.size() - id] = true;
        goodEnt[vt.size() - id] = initEnt();
    }
    play(ent, vt);
    cout << "end";
    return 0;
}

4,继续破解6.6平移线段

运行新程序,发现还是有很多输出,但是大多是无效解,因为题目给的是线段,不是直线。

于是我有了新的思路:把线段当成两个点,不存在直线即可

输入:

0
3
0 0
1 1
5 0
6
0 0 0 0 1 0
5
1
1 1 -1 -5
0
6
0
1
6 1

0

只花了几秒钟就输出了答案:

花了不到1分钟输出了end,表明这是唯一解。

对应答案:(假如游戏的点的坐标和我输入的一样,是0 0、1 1、5 0)

然而实际上,游戏给的点大概是0 0、1 1、1.5 0,所以实际答案是:

5,6.8平行四边形

   

根据最后一步是上面水平的线还是右边的斜线,可以分为2种情况:

(1)最后一步是上面水平的线

也就是说,第4步是左边的斜线,第6步是右边的斜线,第8步是上面水平的线。

根据直觉,这种情况的概率较小,暂时跳过。

(2)最后一步是右边的斜线

也就是说,到第6步的时候就应该画完了左边的斜线和上面水平的线。

输入:

0
3
0 0
1 1
14.5  18.5
8
1 0 0 1 0 1 0 1
6
0
1
14 18
8
0
1
15 19
0

输出了很多很多答案,应该任取一个即可。

貌似最后几步不准确,因为我只校验了点,没有校验线。(这就是为什么输出了很多很多答案)

但是这并不妨碍前面5步都是完全正确的,所以我们得到了正确答案:

6,6.9欧拉九点圆

   

输入:

4
1 1 -1 0
1 4.1 1 0
1 0.7 1 -3.4
1 1 1 -2
5
0 0
-1 4.1
2 2
1 1
-0.64516129  2.64516129
5
0 0 1 0 1
5
0
1
0.569117647  1.9808823529
0
程序运行了较长时间,最终因为内存爆了终止了,我做了计数,哈希表容量在102万的时候就崩溃了。

九,继续优化

1,把静态局部变量改为全局变量

map<set<vector<int>>, int>m;
void play(Entity ent, vector<TYPE> vt)
{
......
}

2,优化哈希方案——两级哈希

把局面标准化为set<vector<int>>,再用哈希表存起来的话,占用的内存太大。

这里我采用了两级哈希的方案:先把圆和直线映射到一个整数,再用set<int>表示一个局面,再把这个set映射到一个整数

map<vector<int>, int>mv;
int normallizeNode(Node s)
{
    if (s.type == LINE) {
        normallizeLine(s);
    }
    vector<int>v(4);
    v[0] = s.type, v[1] = int(s.a * 1000), v[2] = int(s.b * 1000), v[3] = int(s.c * 1000);
    if (mv[v])return mv[v];
    return mv[v] = mv.size();
}

set<int>normallizeNode(vector<Node>vec)
{
    set<int>s;
    for (unsigned i = 0; i < vec.size(); i++)s.insert(normallizeNode(vec[i]));
    return s;
}

3,线段和直线

九点圆问题,初始局面就是一个任意的三角形,但是仍然涉及到一个问题:

由于三角形给的是线段而不是直线,那么是否存在一种解法,对于三角形的形状并不具有普适性呢?

比如说,有没有某个尺规作图问题,它有一种解法,这个解法对于(某个)锐角三角形的三个(或两个)方向都是OK的,但是对于直角三角形,必须以斜边为底边才是可行的?

由于这个不确定因素的存在,导致在给一个局面数据化的时候,把线段看成两点还是看成一个直线,还是需要根据实际问题去推测。

4,重新数据化

如果线段和直线的问题解决了,或者说我们假设初始局面给的本就是三条直线相交得到的三角形,那么,这样的局面肯定是比单纯的三角形更容易得到解法的。

类似的,如果初始局面是一个等腰三角形,那么肯定也是更容易得到解法的。(再次更新本博文,这一观点不对,等腰三角形反而有可能找不到普通三角形可以找到的解法

输入:

4
1 1 -1 0
1 3 2 0
1 2 3 -10
1 1 1 -2
4
0 0
2 2
-4 6
1 1
5
0 0 1 0 1
5
0
1
-0.3 2.3
0

在哈希表容量是246万的时候,程序还是崩溃了,仍然是内存爆了,不过好歹也是有不少提升了。

而且,在内存爆掉之前,程序输出了唯一一组解:

y = 1.000000x + 0.000000
y = -1.500000x + -0.000000
y = -0.666667x + 3.333333
y = -1.000000x + 2.000000
(x - 0.000)^2 + (y - 0.000)^2 = 8.000
(x - 0.000)^2 + (y - 0.000)^2 = 52.000
y = 0.067797x + -1.864407
(x - -6.825)^2 + (y - -2.327)^2 = 52.000
y = -0.130435x + 2.260870

5,可视化——在线作图

我找到了这个网站:https://zuotu.91maths.com

可以在线作图,只需要把函数表达式输入进去即可。

在我的输出里面,直线已经是y=...了,直接复制就完事了。

圆的话,需要用2个半圆表示,2个半圆都可以用y=...的形式表示出来。

为了方便后续用这个网站作图,我直接稍微修改了我的输出形式。

void display(Node s)
{
    if (s.type == CIRCLE) {
        printf("y = %.3f +/- sqrt(%.3f - (x - %.3f)^2)\n", s.b, s.c * s.c, s.a);
    }
    else {
        normallizeLine(s);
        printf("y = %.6fx + %.6f\n", s.a, s.c);
    }
}

然而,根据我的分析,这个解法应该是只对等腰三角形有效,对于普通三角形并没有用。

6,优化第一步

三角形加上垂直平分线有5个点,第一步是画圆,圆由2个不同的点确定,所以第一步有5*4=20种情况

这里搬出我的信条:数学的美,在于极致的纯粹,超越直觉而存在的优美的对称性,蕴含着最本质的规律

所以我认为,A和C是完全对称的,即使E在AD上而不在CD上。

进一步可以把20种情况优化到13种情况:AB、AC、AD、AE、BA、BD、BE、DA、DB、DE、EA、EB、ED

其中AC是肯定不可能的,因为作垂直平分线的逻辑和圆AC是重复的,而这个游戏给出的E任务一定是最简E任务。

所以第一步有12种情况:AB、AD、AE、BA、BD、BE、DA、DB、DE、EA、EB、ED

效率不高,思路先保留。

7,优化最后一步

如果加入新的判定方案,可以再让搜索树再减少一层,效率很高,开干!

判断点是否在直线或者圆上:

bool pointOnLine(Point p, Node s)
{
    return sameValue(s.a*p.x + s.b*p.y + s.c, 0);
}
bool pointOnCircle(Point p, Node s)
{
    return sameValue((s.a-p.x)*(s.a-p.x) + (s.b-p.y)*(s.b-p.y) - s.c*s.c, 0);
}
bool pointOnNode(Point p, Node s)
{
    return s.type==CIRCLE?pointOnCircle(p,s):pointOnLine(p,s);
}

判断点是否在当前局面:

bool pointInEntity(Point p, Entity ent)
{
    for (auto it = ent.vecNode.begin(); it != ent.vecNode.end(); it++) {
        if(pointOnNode(p,*it))return true;
    }
    return false;
}

我本以为,只需要判断最后一个产生的几何体(圆或直线)上有没有这个点即可。但是再仔细一想,似乎并不是这样,这一点先不纠结。

封装CIN,让数据配置可以写文本注释:

#define CIN(x) while (!(cin >> x)) { \
        cin.clear();      \
        cin.ignore();     \
    }
#define CIN2(x, y) CIN(x)CIN(y)
#define CIN3(x, y, z) CIN(x)CIN(y)CIN(z)

保持分步判定,判定规则拓展为3种规则:判定是否存在几何体、判断是否存在点、判定是否存在经过定点的几何体

这样判定的话,等腰三角形的垂直平分线从一开始就经过了九点圆心,没法筛选,所以我还是采用了之前建的坐标(之前的九点圆心坐标感觉是不是写错了):

初始局面
4 圆和直线的个数,下面每一行是一个圆或直线的方程
1 1 -1 0
1 4.1 1 0
1 0.7 1 -3.4
1 1 1 -2
5 点的个数,下面每一行是一个点的坐标
0 0
-1 4.1
2 2
1 1
-0.64516129  2.64516129
操作集 
5 操作次数
0 0 1 0 1
分步判定
4 执行多少步之后需要判定
0 判定是否存在几何体
0 判定是否存在点
1 判定是否存在经过定点的几何体
0.569117647  1.9808823529 

0
1
0.569117647  1.9808823529
0
0

之前用Entity表示判定集其实是取巧的,现在因为判定集有2个点集,就不能再用了。

typedef struct ExEntity {
    vector<Node> vecNode;
    vector<Point> vecPoint;
    vector<Point> vecPoint2;
} ExEntity;

vector<ExEntity> goodEnt;
bool isGoodEntity(Entity ent, int id)
{
    for (auto it = goodEnt[id].vecNode.begin(); it != goodEnt[id].vecNode.end(); it++) {
        if (!alreadyHaveNode(ent, *it)) {
            return false;
        }
    }
    for (auto it = goodEnt[id].vecPoint.begin(); it != goodEnt[id].vecPoint.end(); it++) {
        if (!alreadyHavePoint(ent.vecPoint, *it)) {
            return false;
        }
    }
    for (auto it = goodEnt[id].vecPoint2.begin(); it != goodEnt[id].vecPoint2.end(); it++) {
        if (!pointInEntity(*it, ent)) {
            return false;
        }
    }
    return true;
}

在哈希表容量是近4000万的时候,程序运行结束了,至于为什么之前在246万就爆内存了,新程序在近4000万还没爆内存,我想,估计是影响内存的不仅仅是哈希表,还有其他数据吧。

输出:

y = 1.000000x + 0.000000
y = -4.100000x + -0.000000
y = -0.700000x + 3.400000
y = -1.000000x + 2.000000
y = 1.000 +/- sqrt(2.000 - (x - 1.000)^2)
y = 1.427 +/- sqrt(2.000 - (x + 0.348)^2)
y = 3.155059x + 0.185283
y = 2.282 +/- sqrt(2.000 - (x - 1.597)^2)
y = -2.276451x + 3.276451

y = 1.000000x + 0.000000
y = -4.100000x + -0.000000
y = -0.700000x + 3.400000
y = -1.000000x + 2.000000
y = 1.000 +/- sqrt(2.000 - (x - 1.000)^2)
y = 1.427 +/- sqrt(2.000 - (x + 0.348)^2)
y = 3.155059x + 0.185283
y = 2.282 +/- sqrt(2.000 - (x - 1.597)^2)
y = -0.465969x + 2.246073

2个都是正确的解。

8,优化剪枝方案

这里介绍一些概念和我的推测

(1)点被使用:每一步都是用两个点,要么就是两点作圆,要么就是两点作直线,点可以被重复使用。

(2)几何体被使用:每个几何体(除了最终目标)产生的点至少要在后续被使用。

(3)有效点:最终被使用的点叫有效点(新增一个几何体,一般产生的大多是无效点)
推测一:每个新产生的点最多被使用两次
(4)推理图
(5):设有有向图D的一个节点子集S,若S是符合下列条件的极小集:S中节点两两互不相邻,且D的每一节点或在S中,或存在一条弧从S中一节点指向它,则称S为D的1基,也称为核。
(6)推理图的核:要么是一个点,要么是两个点。
(7)推理图的分层
(8)进化节奏:实体分批产生的一个过程,每一批至少一个实体,产生这些实体
推测二:每个实体只在进化的某一层中产生有效新点,即推理图是一棵树。

9,完整代码

#include <iostream>
#include <algorithm>
#include <math.h>
#include <map>
#include <set>
#include <vector>
using namespace std;

#define CIN(x) while (!(cin >> x)) { \
        cin.clear();      \
        cin.ignore();     \
    }
#define CIN2(x, y) CIN(x)CIN(y)
#define CIN3(x, y, z) CIN(x)CIN(y)CIN(z)

typedef struct Point {
    double x, y;
} Point;
 
typedef enum TYPE {
    CIRCLE = 0,
    LINE = 1,
    BUFF
}TYPE;
 
typedef struct Node {
    TYPE type;
    double a, b, c;
} Node;
 
typedef struct Entity {
    vector<Node> vecNode;
    vector<Point> vecPoint;
} Entity;

typedef struct ExEntity {
    vector<Node> vecNode;
    vector<Point> vecPoint;
    vector<Point> vecPoint2;
} ExEntity;
 
//两点距离平方和
double getDistanceSquar(Point a, Point b)
{
    return pow(a.x - b.x, 2) + pow(a.y - b.y, 2);
}
//两点距离
double getDistance(Point a, Point b)
{
    return sqrt(getDistanceSquar(a, b));
}
//画圆
Node makeCircle(Point center, Point a)
{
    return Node{ CIRCLE,center.x, center.y, getDistance(center, a) };
}
//画直线
Node makeLine(Point a, Point b)
{
    return Node{ LINE,b.y - a.y, a.x - b.x, a.y * b.x - a.x * b.y };
}
Node make(TYPE t, Point a, Point b)
{
    if (t == CIRCLE) {
        return makeCircle(a, b);
    }
    else {
        return makeLine(a, b);
    }
}
 
#define dps 0.00001
Point invalidPoint = { 0,12345 };
bool sameValue(double x, double y)
{
    return abs(x - y) < dps;
}
// 直线的判定式
double deltaLineAndLine(Node s, Node t)
{
    return s.a * t.b - s.b * t.a;
}
// 两直线是否平行
bool isParallels(Node s, Node t)
{
    return sameValue(deltaLineAndLine(s, t), 0);
}
// 两直线交点
pair<Point, Point> lineAndLine(Node s, Node t)
{
    if (isParallels(s, t)) {
        return make_pair(invalidPoint, invalidPoint);
    }
    Point p = { (t.c * s.b - s.c * t.b) / deltaLineAndLine(s, t), (s.c * t.a - s.a * t.c) / deltaLineAndLine(s, t) };
    return make_pair(p, invalidPoint);
}
 
// 一般式化为斜率式
void normallizeLine(Node& s)
{
    s.a /= -s.b, s.c /= -s.b, s.b /= -s.b;
}
// 圆和直线相交的判定式
double deltaCircleAndLine(Node s, Node t)
{
    //normallizeLine(t);
    return s.c * s.c * (t.a * t.a + 1) - pow(t.a * s.a - s.b + t.c, 2);
}
// 圆和线的交点数目,有0,1,2三种情况
int cicleAndLineNum(Node s, Node t)
{
    double delta = deltaCircleAndLine(s, t);
    if (delta < 0) {
        return 0;
    }
    if (delta == 0) {
        return 1;
    }
    return 2;
}
// 圆和线的交点
pair<Point, Point> cicleAndLine(Node s, Node t)
{
    normallizeLine(t);
    int ret = cicleAndLineNum(s, t);
    if (ret == 0) {
        return make_pair(invalidPoint, invalidPoint);
    }
    double sqrtDelta = sqrt(deltaCircleAndLine(s, t));
    Point p1, p2;
    p1.x = (s.a + t.a * s.b - t.a * t.c + sqrtDelta) / (t.a * t.a + 1), p1.y = p1.x * t.a + t.c;
    p2.x = (s.a + t.a * s.b - t.a * t.c - sqrtDelta) / (t.a * t.a + 1), p2.y = p2.x * t.a + t.c;
    return make_pair(p1, p2);
}
 
// 圆的判定式
double deltaCircle(Node s)
{
    return s.a * s.a + s.b * s.b - s.c * s.c;
}
// 两圆相交的两点连成的直线,不需要判定两圆是否相交
Node cicleAndCicleToLine(Node s, Node t)
{
    return Node{ LINE, (t.a - s.a) * 2, (t.b - s.b) * 2, deltaCircle(s) - deltaCircle(t) };
}
//两圆交点
pair<Point, Point>cicleAndCicle(Node s, Node t)
{
    return cicleAndLine(s, cicleAndCicleToLine(s, t));
}
 
//任意俩个Node相交
pair<Point, Point> getIntersectPoint(Node s, Node t)
{
    if (s.type == CIRCLE && t.type == CIRCLE) {
        return cicleAndCicle(s, t);
    }
    if (s.type == LINE && t.type == LINE) {
        return lineAndLine(s, t);
    }
    if (s.type == CIRCLE && t.type == LINE) {
        return cicleAndLine(s, t);
    }
    if (t.type == CIRCLE && s.type == LINE) {
        return cicleAndLine(t, s);
    }
    return make_pair(invalidPoint, invalidPoint);
}
 
//两点重合
bool samePoint(Point a, Point b)
{
    return sameValue(a.x, b.x) && sameValue(a.y, b.y);
}
bool alreadyHavePoint(vector<Point>v, Point p)
{
    for (auto it = v.begin(); it != v.end(); it++) {
        if (samePoint(*it, p)) {
            return true;
        }
    }
    return false;
}
void addPoint(Entity& ent, Point p)
{
    if (!samePoint(p, invalidPoint) && !alreadyHavePoint(ent.vecPoint, p)) {
        ent.vecPoint.push_back(p);
    }
}
void addPoint(Entity& ent, pair<Point, Point>pai)
{
    addPoint(ent, pai.first);
    addPoint(ent, pai.second);
}
void addPoint(Entity& ent, Node s)
{
    for (auto it = ent.vecNode.begin(); it != ent.vecNode.end(); it++) {
        addPoint(ent, getIntersectPoint(*it, s));
    }
}
 
bool sameCircle(Node s, Node t)
{
    return sameValue(s.a, t.a) && sameValue(s.b, t.b) && sameValue(s.c, t.c);
}
bool sameLine(Node s, Node t)
{
    normallizeLine(s);
    normallizeLine(t);
    return sameValue(s.a, t.a) && sameValue(s.c, t.c);
}
bool sameNode(Node s, Node t)
{
    if (s.type != t.type) {
        return false;
    }
    if (s.type == CIRCLE) {
        return sameCircle(s, t);
    }
    return sameLine(s, t);
}
bool alreadyHaveNode(Entity ent, Node s)
{
    for (auto it = ent.vecNode.begin(); it != ent.vecNode.end(); it++) {
        if (sameNode(*it, s)) {
            return true;
        }
    }
    return false;
}
bool addNode(Entity& ent, Node s)
{
    if (alreadyHaveNode(ent, s)) {
        return false;
    }
    addPoint(ent, s);
    ent.vecNode.push_back(s);
    return true;
}
 
void input(TYPE& t)
{
    int x;
    CIN(x);
    t = TYPE(x);
}
void input(Node& s)
{
    input(s.type);
    CIN3(s.a,s.b,s.c);
}
void input(Point& p)
{
    CIN2(p.x,p.y);
}
 
template<typename T>
void initVector(vector<T>& v)
{
    int len;
    CIN(len);
    v.resize(len);
    for (int i = 0; i < len; i++) {
        input(v[i]);
    }
}
 
Entity initEnt()
{
    Entity ent;
    initVector<Node>(ent.vecNode);
    initVector<Point>(ent.vecPoint);
    return ent;
}
 
vector<TYPE> initVecType()
{
    vector<TYPE>v;
    initVector<TYPE>(v);
    return v;
}
 
bool pointOnLine(Point p, Node s)
{
    return sameValue(s.a * p.x + s.b * p.y + s.c, 0);
}
bool pointOnCircle(Point p, Node s)
{
    return sameValue((s.a - p.x) * (s.a - p.x) + (s.b - p.y) * (s.b - p.y) - s.c * s.c, 0);
}
bool pointOnNode(Point p, Node s)
{
    return s.type == CIRCLE ? pointOnCircle(p, s) : pointOnLine(p, s);
}
bool pointInEntity(Point p, Entity ent)
{
    for (auto it = ent.vecNode.begin(); it != ent.vecNode.end(); it++) {
        if(pointOnNode(p,*it))return true;
    }
    return false;
}
 
vector<bool> endEnt;
vector<ExEntity> goodEnt;
bool isGoodEntity(Entity ent, int id)
{
    for (auto it = goodEnt[id].vecNode.begin(); it != goodEnt[id].vecNode.end(); it++) {
        if (!alreadyHaveNode(ent, *it)) {
            return false;
        }
    }
    for (auto it = goodEnt[id].vecPoint.begin(); it != goodEnt[id].vecPoint.end(); it++) {
        if (!alreadyHavePoint(ent.vecPoint, *it)) {
            return false;
        }
    }
    for (auto it = goodEnt[id].vecPoint2.begin(); it != goodEnt[id].vecPoint2.end(); it++) {
        if (!pointInEntity(*it, ent)) {
            return false;
        }
    }
    return true;
}
 
void display(Node s)
{
    if (s.type == CIRCLE) {
        if(s.a>=0)printf("y = %.3f +/- sqrt(%.3f - (x - %.3f)^2)\n", s.b, s.c * s.c, s.a);
        else printf("y = %.3f +/- sqrt(%.3f - (x + %.3f)^2)\n", s.b, s.c * s.c, -s.a);
    }
    else {
        normallizeLine(s);
        printf("y = %.6fx + %.6f\n", s.a, s.c);
    }
}
 
void display(Entity ans)
{
    for (unsigned i = 0; i < ans.vecNode.size(); i++) {
        display(ans.vecNode[i]);
    }
}
 
map<vector<int>, int>mv;
int normallizeNode(Node s)
{
    if (s.type == LINE) {
        normallizeLine(s);
    }
    vector<int>v(4);
    v[0] = s.type, v[1] = int(s.a * 1000), v[2] = int(s.b * 1000), v[3] = int(s.c * 1000);
    if (mv[v])return mv[v];
    return mv[v] = mv.size();
}
 
set<int>normallizeNode(vector<Node>vec)
{
    set<int>s;
    for (unsigned i = 0; i < vec.size(); i++)s.insert(normallizeNode(vec[i]));
    return s;
}
 
map<set<int>, int>m;
void play(Entity ent, vector<TYPE> vt)
{
    if (m.size() % 1000 == 0) {
        cout << m.size();
    }
    if (m[normallizeNode(ent.vecNode)])return; // 剪枝
    if (vt.size() < endEnt.size() && endEnt[vt.size()]) {
        if (!isGoodEntity(ent, vt.size())) {
            return;
        }
    }
    if (vt.empty()) {
        display(ent);
        cout << endl;
        return;
    }
    for (int i = 0; i < ent.vecPoint.size(); i++) {
        int startId = ((vt[0] == LINE) ? i : 0);  // 剪枝
        for (int j = startId; j < ent.vecPoint.size(); j++) {
            if (j == i)continue;
            Node node = make(vt[0], ent.vecPoint[i], ent.vecPoint[j]);
            Entity tmp = ent;
            TYPE temp = vt[0];
            if (!addNode(ent, node)) {
                ent = tmp;
                continue;
            }
            vt.erase(vt.begin());
            play(ent, vt);
            ent = tmp;
            vt.insert(vt.begin(), temp);
        }
    }
    m[normallizeNode(ent.vecNode)] = 1;
}
 
int main()
{
    Entity ent = initEnt();
    vector<TYPE> vt = initVecType();
    endEnt.resize(vt.size());
    goodEnt.resize(vt.size());
    int id;
    while (true) {
        CIN(id);
        if (id <= 0)break;
        endEnt[vt.size() - id] = true;
        initVector<Node>(goodEnt[vt.size() - id].vecNode);
        initVector<Point>(goodEnt[vt.size() - id].vecPoint);
        initVector<Point>(goodEnt[vt.size() - id].vecPoint2);
    }
    play(ent, vt);
    cout << "end";
    return 0;
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值