/*此C++程序计数并显示1 2 6 40 303 2929正方形上不协调的无标号无向树n=1,2,3,。。,节点和n-1条边。http://oeis.org/A056787理查德·马塔尔,2006-04-13*/#包括#包括#包括#包括#包括使用命名空间标准;/**节点是方形晶格上的一个点。*节点只包含*方形格子。*/类节点{公众:/*xy[0]是x,xy[1]是坐标的y分量*/短xy[2];节点(){xy[0]=xy[1]=SHRT_MIN;}/**具有一对坐标的构造函数。*@param x点的x坐标*@param y点的y坐标*/节点(短x,短y){xy[0]=x;xy[1]=y;}/**给定原始节点转换为方向的构造函数。*@param n要从中启动的节点*@param将一个从0到4的参数指定为*在此创建的节点的节点方向*将被放置。*/节点(const节点-n,int目录){/**对于从0到3的\c dir参数值*我们创建新节点,一步进入东部(+x),*从原始位置向北(+y)、向西(-x)或向南(-y)。*/开关(dir){案例0:xy[0]=n.xy[0]+1;xy[1]=n.xy[1];断裂;案例1:xy[0]=n.xy[0]+1;xy[1]=n.xy[1]+1;断裂;案例2:xy[0]=n.xy[0];xy[1]=n.xy[1]+1;断裂;案例3:xy[0]=n.xy[0]-1;xy[1]=n.xy[1]+1;断裂;案例4:xy[0]=n.xy[0]-1;xy[1]=n.xy[1];断裂;案例5:xy[0]=n.xy[0]-1;xy[1]=n.xy[1]-1;断裂;案例6:xy[0]=n.xy[0];xy[1]=n.xy[1]-1;断裂;案例7:xy[0]=n.xy[0]+1;xy[1]=n.xy[1]-1;断裂;}}/**通过八个节点中的一个创建从该节点派生的新节点*正方形的对称运算。*@param mode 0到7之间的值,表示对称操作*用于放置成像节点。*/节点congru(const int模式)const{/**参数\c模式从0到7生成一个新节点*原始的(模式=0),围绕(0,0)中心旋转*坐标的90度倍数(模式=1到4),或*在两步过程中创建,首先在*坐标的主对角线(x=y),然后将其旋转到*90度的倍数以外的地方。*/开关(模式){案例0:返回*this;断裂;案例1://旋转90度返回节点(-xy[1],xy[0]);断裂;案例2://旋转180度返回节点(-xy[0],-xy[1]);断裂;案例3://旋转270度返回节点(xy[1],-xy[0]);断裂;案例4://后视镜主对角线返回节点(xy[1],xy[0]);断裂;案例5://后视镜主对角线然后旋转90度返回节点(-xy[0],xy[1]);断裂;案例6://后视镜主对角线然后旋转180度返回节点(-xy[1],-xy[0]);断裂;案例7://后视镜主对角线然后旋转270度返回节点(xy[0],-xy[1]);断裂;}}受保护的:私人:} ;/**节点排序运算符。*这通过有关方面的排序引入了节点列表的排序*到y坐标(较小的y被解释为较小的节点),并且*如果y坐标相等,则相对于x坐标。*@param n1次符号左边的节点。*@param n2次符号右边的节点。*/布尔运算符<(const节点&n1,const节点-n2){如果(n1.xy[1]<n2.xy[1])返回true;否则,如果(n1.xy[1]>n2.xy[1])返回false;否则,如果(n1.xy[0]<n2.xy[0])返回true;其他的返回false;}/**节点排序运算符。*如果两个节点具有相同的x坐标和相同的y坐标,则它们是相等的。*@param n1等号左边的节点。*@param n2等号右边的节点。*/布尔运算符==(const节点&n1,const节点-n2){返回(n1.xy[0]==n2.xy[0]&&n1.xy[1]==n2.xy[1]);}/**边缘类是由边缘连接的一对节点。*连接是隐式的,只有*存储一对节点。*/类边缘{公众:/*假定节点对的排序为节点[0]<节点[1]。*要形成边,节点[0]=节点[1]。*/节点节点[2];/**具有一对节点的构造函数。*@param n1两个节点之一。*@param n2两个节点中的另一个。*/边(常数节点n1,常数节点n2){/*为了确保正确的排序,我们调用了一个接口函数*如果需要,可以交换这两个参数*/初始(n1,n2);}/**具有节点和方向指示器的构造函数*在其中找到另一个节点。*@param n用于边缘的一个节点。*@param dir方向指示器从0到7。这个组合是一个*四个潜在方向的风向玫瑰指示器*正方形格子。*/边(常量节点n,int目录){#ifdef旧/*这本质上是建筑的缓慢版本*因为它涉及到与init()函数的额外比较。*/开关(dir){案例0:初始化(n,节点(n.xy[0]+1,n.xy[1]));断裂;案例1:初始化(n,节点(n.xy[0]+1,n.xy[1]+1));断裂;案例2:初始化(n,节点(n.xy[0],n.xy[1]+1));断裂;案例3:init(n,节点(n.xy[0]-1,n.xy[1]+1));断裂;案例4:初始化(n,节点(n.xy[0]-1,n.xy[1]));断裂;案例5:初始化(n,节点(n.xy[0]-1,n.xy[1]-1));断裂;案例6:初始化(n,节点(n.xy[0],n.xy[1]-1));断裂;案例7:初始化(n,节点(n.xy[0]+1,n.xy[1]-1));断裂;}#其他/*对于从0到7的dir值,我们在中创建配对节点*节点的E、NE、N、NW、W、SW、S、SE方向(+x、+y、-x、-y)*已明确提供。*/开关(dir){案例0:节点[0]=n;节点[1]=节点(n.xy[0]+1,n.xy[1]);断裂;案例1:节点[0]=n;节点[1]=节点(n.xy[0]+1,n.xy[1]+1);断裂;案例2:节点[0]=n;节点[1]=节点(n.xy[0],n.xy[1]+1);断裂;案例3:节点[0]=n;节点[1]=节点(n.xy[0]-1,n.xy[1]+1);断裂;案例4:节点[1]=n;节点[0]=节点(n.xy[0]-1,n.xy[1]);断裂;案例5:节点[1]=n;节点[0]=节点(n.xy[0]-1,n.xy[1]-1);断裂;案例6:节点[1]=n;节点[0]=节点(n.xy[0],n.xy[1]-1);断裂;案例7:节点[1]=n;节点[0]=节点(n.xy[0]+1,n.xy[1]-1);断裂;}#结尾}/**这构建了现有节点的一致版本。*@param mode在nodes.congru()中使用的值形式为0到7,表示*生成*旋转和反射节点的图像。*@return相对于*原产地。*/边congru(const int模式)const{返回边(节点[0].congru(模式),节点[1].conguru(模式));}/**平移边。*返回时,边被\c dx和dy移动到方形网格上的两个方向。*@param dx向x方向运动的整数值*@param dy向y方向运动的整数值*/空移(短dx,短dy){节点[0].xy[0]+=dx;节点[1].xy[0]+=dx;节点[0].xy[1]+=dy;节点[1].xy[1]+=dy;}受保护的:私人:/**通过节点进行复制,确保先放置较小的节点。*@param n1用于新边缘的第一个节点*@param n2用于新边缘的第二个节点*/void init(常量节点&n1,常量节点&n){/*节点比较运算符用于确保一致性*边内节点短向量中两个节点的排序。*/如果(n1<n2){节点[0]=n1;节点[1]=n2;}其他的{节点[0]=n2;节点[1]=n1;}}} ;/**边缘排序运算符。*第一个节点较小的边被视为*两条边。如果第一个节点在两条边之间共享,则*在第二个节点上进行比较。*@param e1要比较的两条边中的第一条。*@param e2要比较的两条边中的第二条。*如果第一条边被认为是较小的边,则@return true。*/布尔运算符<(const edge&e1,const edge&e2){如果(e1.nodes[0]<e2.nodes[0])返回true;else if(e1.nodes[0]==e2.nodes[0]){if(e1.节点[1]<e2.节点[1])返回true;其他的返回false;}其他的返回false;}/**边缘排序运算符。*如果节点相同,则边相同。*@param e1要比较的两条边中的第一条。*@param e2要比较的两条边中的第二条。*如果边相同,@return true。*/bool运算符==(const edge&e1,const edge&e2){/*由于边是在其节点中排序的,因此只需比较第一个和*第二个节点分开,不是全部四对节点。。。这是一次加速的尝试*启动程序。*/return(e1.nodes[0]==e2.nodes[0]&&e1.nodes[1]==e2.节点[1]);}/**树是边的列表/向量,所有这些边都不同*共享节点,这样就可以在轨迹上横穿图形*可以到达每个节点。。*/类树{公众:/*所有边的向量是树的唯一完整表示*/矢量边缘;/*\c bbox包含一个边界框,因此bbox[0]是最小的x,*bbox[1]最小的y,bbox[2]最大的x和bbox[3]的最大y值*\c边的所有节点。*/短bbox[4];/*默认构造函数创建一个空树。*它对生成树的向量很有用,没有其他意义。*/树(){}/**函数congru()创建了当前树的同余图像*@param mode在edge::congru()意义上是一个从0到7的数字*指定方形的8个对称操作中的哪一个(模式=0是副本)*用于生成新树。*@return旋转/反射树*/树congru(const int模式)const{树木结果;/*该算法只是简单地分别创建每个边缘的图像,*并使用addedge()将其添加到新的树中。*/for(int e=0;e<int(edges.size()));e++){const-edge-cedge=边[e].congru(模式);结果加码(cedge);}/*典型的旋转会在*坐标系。为了更快地比较同余对(*根据算法消除),我们将每棵树转换为*它被放置在具有最小x和最小x的右上象限中*所有节点的y坐标均为0。*/结果规范化();返回结果;}/**在树中再创建一条边。这是在没有*任何进一步的检查,例如确保边缘连接到*已经存在的树干,或者确保它还不是树的一部分。*@param e要添加的边*/无效加边(常数边&e){边。后推(e);}/**调查两棵树是否一致。*如果此实例和其他实例的所有边都为真*可以通过平移、旋转或镜像操作相互放置。*@param oth要与当前实例进行比较的另一棵树。*假设其\c bbox参数是最新的。*@如果这棵树和另一棵树是一致的,则返回true。*/bool iscongru(const树和oth){/*为了避免比较过程中的一些开销,我们比较了*粗糙参数优先:如果两棵树的边数不同,*树木不同。*/if(edges.size()!=oth.edges.size())返回false;/*旋转和反射的一致操作可以互换*边界框x和y坐标,但不更改它们。*因此,我们得出结论,如果匹配测试失败,则这两棵树会有所不同。*/如果(bbox[2]!=oth.bbox[2]&&bbox[2])返回false;if(bbox[3]!=oth.bbox[2]&&bbox[3]!=oth.bbox[3])返回false;/*决定一致性的最后一个更稳定的操作是创建*oth的所有8个一致图像,并将它们与*当前实例。为了加速比较,我们对边缘列表进行排序*当前实例和由另一个实例创建的8个图像的每个边缘列表*实例。这应该会导致比较时间在*边数。*/排序(edges.begin()、edges.end());/*这是对另一棵树所有可能的8个同余版本的循环*/for(int c=0;c<8;c++){/*通过调用congru()获得另一棵树的临时法师副本*/树imag=oth.congru(c);/*对图像副本的边缘列表进行排序,并确定*然后对STL库的两个调用执行相同的图像列表。*如果我们发现8个版本中的任何一个都与当前实例相同,*我们不需要调查其他版本,可能会得出结论*两棵树是一致的。*/排序(imag.edges.begin()、imag.edgers.end());if(等于(edges.begin()、edges.end()和imag.edges.贝金()))返回true;}/*如果我们在8张图片上掉进了循环,这些都没有*是一致的,结果是否定的。*/返回false;}/**一个函数,用于生成比现有实例多一条边的所有树。*当前实例是从中添加所有可能方法的父实例*测试任何现有节点的另一条边。孩子们被减少到*一组相互不协调的树,以向量形式返回。*@return生成的不协调树的向量。*/矢量儿童(){/*\c resul收集所有新树,其边缘比当前树多一个*/矢量结果;/*为了提高效率,我们生成现有树的所有节点的列表,*通常由1到4条边共享。其目的是繁殖*从这四个方向中的任何一个方向开始的新边*被正方形格子所接纳。*/常数向量thisnod=allnodes();//cout<<thisnod.size()<<“nodes”<<edges.size()<>“edges\n”;/*在所有现有节点的循环中,我们可以生成所有子树*与当前骨架相同,但多了一条边。*这是可行的,因为树是一个连通图,所以不能隔离边。*/for(int n=0;n<int(thisnod.size()));n++){/*对于节点的八个方向中的每一个,我们测试是否*可以是潜在新子树的新边的方向。*/for(int dir=0;dir<8;dir++){#ifdef旧/*这是下面算法的一个更昂贵(较慢)的版本*/常数边tste(thisnod[n],dir);if(tstedge(tste)){树坎迪(*this);坎迪加德奇(tste);candi.normalize();bool congru=假;for(int pres=0;pres<int(resul.size()));压力++){if(结果[pres].iscongru(candi)){congru=真;断裂;}}if(!congru)恢复推送返回(candi);}#其他/*对于这个特定的节点和方向,我们测试*节点和新节点是扩展的候选节点。我们生成*新节点进入特定方向,并将其作为新节点*如果这个新站点还没有被占用,请使用edge。*/常量节点tste(thisnod[n],dir);/*使用havnode()测试方格中点的占用情况*/if(!havnode(tste)){/*如果此节点尚未被占用(由于树是无周期的,因此允许使用),*我们首先复制此树,然后添加*在当前节点和新节点之间展开的新边。*/树坎迪(*this);坎迪加德奇(边缘(thisnod[n],tste));/*添加边可能会将新树扩展到*象限向左或向下,因此我们将树向右移动*或者使用normalize()将其放入第一象限*坐标系的。*/candi.normalize();/*此新子树可能已存在于树列表中*之前生成。我们测试它是否与任何*到目前为止,子树已经积累在结果向量中。*/bool congru=假;for(int pres=0;pres<int(resul.size()));压力++){if(结果[pres].iscongru(candi)){congru=真;断裂;}}/*如果在*当前的子列表,我们将其放入结果列表。*/if(!congru)结果回推(candi);}#结尾}}/*返回相互不一致的子级的结果列表*/返回结果;}受保护的:私人:/*边界框\c bbox被更新以反映实际*树的大小。*/无效更新BB(){/*该算法扫描每条边和边中的两个节点*用于x和y中的最大和最小坐标,并更新*边向外延伸的四个边界框参数*边界框看起来很好。*/if(edges.size()){bbox[2]=bbox[0]=边[0].节点[0].xy[0];bbox[3]=bbox[1]=边[0].节点[0].xy[1];}for(int e=0;e<int(edges.size()));e++){bbox[0]=最小值(bbox[0],边[e].节点[0].xy[0]);bbox[0]=最小值(bbox[0],边[e].节点[1].xy[0]);bbox[1]=最小值(bbox[1],边[e].节点[0].xy[1]);//bbox[1]=最小值(bbox[1],edges[e].nodes[1].xy[1]);节点已排序:足以查看节点[0]的最小y分量bbox[2]=最大值(bbox[2],边[e].节点[0].xy[0]);bbox[2]=最大值(bbox[2],边[e].节点[1].xy[0]);//bbox[3]=最大值(bbox[3],边[e].节点[0].xy[1]);节点已排序:足以查看节点[1]的最大ybbox[3]=最大值(bbox[3],边[e].节点[1].xy[1]);}}/**归一化意味着树坐标的刚性平移*(指所有边上的所有节点)使树紧密贴合*第一象限和所有节点上的最小x和y坐标分别为零和零。*/无效规范化(){更新BB();if(bbox[0]|bbox[1]){常数短dx=-bbox[0];常数短dy=-bbox[1];for(int e=0;e<int(edges.size()));e++)边[e].移位(dx,dy);bbox[0]=bbox[1]=0;bbox[2]+=dx;bbox[3]+=dy;}}/*测试\c n是否已经是树的一部分。*@param n要在当前实例中找到或找不到的节点。*如果节点是树中任何边的一部分,则@return true。*/bool havnode(常量节点&n)常量{/*在一个简单的线性搜索策略中,所有边和每个边的两个节点*将它们中的个与边进行比较。*/for(int e=0;e<int(edges.size()));e++)if(边[e].nodes[0]==n |边[e]节点[1]==n)/*如果我们在树中找到与候选者匹配的节点,*为了提高效率,我们尽早报告这一点。*/返回true;/*在程序的这一点上,我们已经经历了双重循环*边和节点,未找到任何匹配项。所以结果是“不”。*/返回false;}#ifdef旧/**测试“e”是否是新边缘的可接受/兼容选择*/bool tstedge(常数边&e)常数{const bool haven1=havnode(e.nodes[0]);const bool haven2=havnode(e.nodes[1]);/*如果有节点0但没有节点1,则返回true,反之亦然*/return(haven1!=haven2);}#结尾/**测试一条边作为当前树中新边的候选边。*@param n树的当前实例的一个节点。*@param dir沿新边扩展的查找方向(0到3)。*@return true(等于accepted)表示新站点*对于合作伙伴节点,\c n尚未被另一个节点占用*在当前树中。*/bool tstedge(const节点&n,int目录)const{返回!havnode(节点(n,dir));}/**生成当前树中所有节点的列表。*@return当前树的所有节点(已删除重复项)的向量。*/矢量allnodes()常量{/**这是结果向量,创建时为空*/矢量结果;/*我们线性搜索每个包含两个节点的所有边*/for(int e=0;e<int(edges.size()));e++){/*STL的算法::find()操作用于比较*当前的每个节点都有一组已经看到的节点*通过访问前面的边。*/矢量::迭代器fi=resul.begin();矢量::迭代器la=resul.end();if(find(fi,la,edges[e].nodes[0])==resul.end())结果push_back(边[e].节点[0]);fi=结果开始();la=结果end();if(find(fi,la,edges[e].nodes[1])==resul.end())结果push_back(边[e].节点[1]);}返回结果;}} ;/**树比较运算符。*@param t1是第一棵树,应该在第一象限中规范化。*@param t2第二棵树,应该在第一象限中规范化*如果两棵树完全重叠(共享相同的边列表),@return true。*/布尔运算符==(树&t1,树&t2){/*不同边缘数的树不同*/if(t1.edges.size()!=t2.edges.size())返回false;/*不同边界框的树也不同*/否则,如果(t1.bbox[2]!=t2.bbox[2]&&t1.bbo2]!=t1.bbox[3]||t1.bbox[3]!=t2.bbox[2]和t1.bbox[3]!=t2.bbox[3])返回false;其他的{/*如果列表中*根据相同的比较运算符标准进行排序后,*都是一样的。*/排序(t1.edges.begin(),t1.edges.end());排序(t2.edges.begin(),t2.edgers.end());返回相等值(t1.edges.begin(),t1.edges.end(),t2.edges.begins());}}/**树的可读ASCII输出版本。*由点(空白位置)、oh(节点)和破折号(向上或侧向)组成的矩阵*以ASCII艺术方式绘制。*@param t要打印的树。*@param操作要使用的输出流。*/ostream&运算符<<(ostream&os,const-tree&t){常量int xdim=t.bbox[2]+1;//bbox仅指示占用的高度成员常量int ydim=t.bbox[3]+1;char**arr=新char*[2*ydim-1];对于(int r=0;r<2*ydim-1;r++){arr[r]=新字符[2*xdim-1];for(int c=0;c<2*xdim-1;c++)arr[r][c]='.';}for(int e=0;e<int(t.edges.size()));e++){int x0=t.edges[e].nodes[0].xy[0];int y0=t.edges[e].nodes[0].xy[1];arr[2*y0][2*x0]='o';int x1=t.edges[e].nodes[1].xy[0];int y1=t.edges[e].nodes[1].xy[1];arr[2*y1][2*x1]='o';如果(x0==x1)arr[y0+y1][2*x0]=“|”;否则,如果(y0==y1)arr[2*y0][x0+x1]='-';否则,如果(x0+1==x1)if(arr[y0+y1][x0+x1]!=“.”)arr[y0+y1][x0+x1]='X';其他的arr[y0+y1][x0+x1]=“\\”;其他if(arr[y0+y1][x0+x1]!=“.”)arr[y0+y1][x0+x1]='X';其他的arr[y0+y1][x0+x1]=“/”;}对于(int r=0;r<2*ydim-1;r++){for(int c=0;c<2*xdim-1;c++)os<<arr[r][c];os<<endl;}os<<endl;对于(int r=0;r<2*ydim-1;r++)删除[]arr[r];删除[]arr;返回os;}/**通过生成所有同余树创建新一代子树*帕伦的孩子们。*@param修剪现有的父树林*@param verbose if true新的子树将显示在stdout上。*@return所有不协调树的向量,比*paren列表中的那些。*/矢量基因(载体paren,bool verbose){/*结果向量*/矢量结果;/*新的树是通过在循环中依次从每个父树生成的*/for(int t=0;t<int(paren.size()));t++){/*此特定父级的子树收集在中间*森林\c chi。*/矢量chi=paren[t].childs();for(int c=0;c<chi.size();c++){#如果为0cout<<“--------------\n”;cout<<paren[t]<<endl;cout<<“生成\n”;cout<<chi[c]<<endl;cout<<“--------------\n”;#结尾/*这些子树中的每一个都测试了与*结果向量中现有的完整树集。*/bool congru=假;for(int pres=0;pres<int(resul.size()));压力++){if(结果[pres].iscongru(chi[c])){congru=真;断裂;}}/*如果还没有一致的版本,我们添加新的子树,*如果使用了verbose选项,请在stdout上显示并枚举它。*/if(!congru){if(详细){cout<<结果大小()<<“来自”<<t<<“:\n”;cout<<chi[c]<<endl;}结果推回(chi[c]);}}}返回结果;}/**主程序。*只有一个命令行选项-v,这意味着*用ASCII艺术绘制每一代树的明确列表*在标准输出上。否则,只报告原始计数。*/int main(int argc,char*argv[]){/*我们测试-v选项的存在性*/bool verbose=false;字符oc;while((oc=getopt(argc,argv,“v”))!=-1 ){开关(oc){案例'v':verbose=true;断裂;案例“?”:cerr<<“无效命令行选项”<<optopt<<endl;断裂;}}/*基本根树(巨根树)是从(0,0)到(1,0),*以及从(0,0)到(1,1),*只有一条边和两个节点。*/树根1;边strt1(节点(0,0),节点(1,0));root1.addedge(strt1);root1.bbox[0]=0;root1.bbox[1]=0;root1.bbox[2]=1;root1.bbox[3]=0;树根2;边strt2(节点(0,0),节点(1,1));根2加法器(strt2);root2.bbox[0]=0;root2.bbox[1]=0;root2.bbox[2]=1;root2.bbox[3]=1;矢量等级10;等级0.push_back(root1);等级0.push_back(root2);矢量lvl1=基因(lvl0,verbose);#如果为0for(int t=0;t<int(lvl1.size()));t++){cout<<t<<“:\n”;for(整数e=0;e<lvl1[t].edges.size();e++){常数边&ee=等级1[t].边[e];cout<<“from”<<ee.nodes[0].xy[0]<<“”<<ee.nodes[0].xy[1]<<“to”;cout<<ee.nodes[1].xy[0]<<“”<<ee.nodes[1].xy[1]<<endl;}}#结尾cout<<“total”<<lvl1.size()<<endl;/*该程序从所有*根树的子代,是所有子代的第二级*第一层的所有树等。显然可以进一步扩展*超过了8代树木的硬编码最大值。*/矢量lvl2=基因(lvl1,详细);cout<<“total”<<lvl2.size()<<endl;矢量lvl3=基因(lvl2,verbose);cout<<“total”<<lvl3.size()<<endl;矢量lvl4=基因(lvl3,verbose);cout<<“total”<<lvl4.size()<<endl;矢量lvl5=基因(lvl4,verbose);cout<<“total”<<lvl5.size()<<endl;矢量lvl6=基因(lvl5,verbose);cout<<“total”<<lvl6.size()<<endl;矢量lvl7=基因(lvl6,verbose);cout<<“总计”<<lvl7.size()<<endl;矢量lvl8=基因(lvl7,详细);cout<<“total”<<lvl8.size()<<endl;返回0;}