POJ1308-Is It A Tree?
根据上个题最后那个总结的,我只想刷带权并查集啊啊啊啊啊!
无奈顺着AC人数继续刷邝斌飞并查集专题 (这题连个范围都没有,差评)
又重复出现了,买一送一啊,但是一模一样的,不能称之为高频算法题,放个之前重复的总结
洛谷(题目输入少了空格)(难度只是普及-,上一个一模一样的是:普及+/提高)
上一个题AC代码直接改下输出,POJ、可用平台、洛谷三大平台均可AC,AC代码
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #define MAX 100001 5 using namespace std; 6 int pre[MAX]; 7 int num; 8 int find(int x) 9 { 10 if(x==pre[x]) 11 return x; 12 pre[x]=find(pre[x]); 13 return pre[x]; 14 } 15 int union_n[MAX]; 16 int flag; 17 int Case; 18 int main() 19 { 20 Case=1; 21 flag=0; 22 num=0; 23 memset(union_n,0,sizeof(union_n)); 24 int a,b; 25 for(int i=1;i<MAX;i++){ 26 pre[i]=i; 27 } 28 while(cin>>a>>b){ 29 if(a==-1&&b==-1) 30 break; 31 if(a==0&&b==0){ 32 33 if((num==1||num==0) && flag==0) 34 cout<<"Case "<<Case<<" is a tree."<<endl; 35 else 36 cout<<"Case "<<Case<<" is not a tree."<<endl; 37 for(int i=1;i<MAX;i++) 38 pre[i]=i; 39 flag=0; 40 num=0; 41 memset(union_n,0,sizeof(union_n)); 42 Case++; 43 continue; 44 } 45 int root1=find(a); 46 int root2=find(b); 47 if((root1==root2) ) 48 flag=1; 49 else{ 50 pre[root1]=root2; 51 if(union_n[root1]==1){ 52 num--; 53 union_n[root1]=0; 54 } 55 if(union_n[root2]==0){ 56 num++; 57 union_n[root2]=1; 58 } 59 } 60 } 61 }
但HDU WA了,经过different对比,发现题面一模一样,vjudge的Origin也说改个输出就行,至此只能说HDU数据很棒,那三个数据太弱了
这题是有向图,有啥差别啊?我以为每次输入有指向顺序,拿HDUAC博客代码试了下,6 5 6 4 0 0想表示俩父亲,我以为应该不是树,就果是树。艹!!!头尾整岔劈了
6 3 4 3 0 0应该输出不是树。
洛谷这人为啥自己说的是不能俩根,但6 3 4 3 0 0输出是树,找到HACK告诉他?codeforces?
而且vjudge说改下输出的,上面的数据全都过不了!!,这人写的看似挺牛逼的,呵呵,不带脑子刷一万道题也白JB扯!
本来想应该还有个反例类型,比如1指向2,2指向3,再来个3指向1,本来这组数据按照上道题应该输出No,因为上题无向的,我想构造出因为有了方向,导致是树的,但没构造出来
至此回顾查了一下树的性质
之前对性质这些东西都不屑一顾的,跟考研政治一样,跟傻逼银行外包测试行业一样,问你测案例步骤,第一步请领案例,我tm,无用的东西,我不请领案例,我直接CNM,你我咋不问我艹你妈需要几步呢?
曾经我比谁都真诚,被这群外包狗当狗一样耍,呵呵,我不会善罢甘休的
发现看题用手机夸克浏览器的翻译很方便但不如百度翻译精准
找题解手机搜到的结果:博客文章,有时候更好。电脑版反而搜不出来这篇,
这个题目的叙述真的不容易想到,如果一开始先刷的这个题,估计都束手无策
acm题目描述真的玄妙,会很好的给你提示告诉你怎么做用什么算法,但还不那么直接,让你想到很多也能学到很多知识
还有,acm样例牛逼,恰好能提示你些东西,且你一般wa的程序可以过样例,但过不了测评机
这个题目的描述就不像上一个题那么好,不容易想到
当然了,类似之前说过的,严老师是好老师,不给数据是好oj,有坎坷难度是好题,那这个描述隐晦?
有必要看树的性质
树是有向无环图,要保证连通性,任意两个节点间有且仅有一条路径。
这里解释下,比如1指向2,1指向3,1是2和3的根,你tm告诉我,2和3之间哪有路径,
科普:
在树结构中,如果节点1指向节点2,同时节点1也指向节点3,那么虽然节点2和节点3之间没有直接的边相连,但它们之间仍然可以通过节点1找到一条路径。这条路径就是:从节点2出发,向上回到节点1,然后再从节点1向下走到节点3
在树结构中呀,路径是可以包括向上(朝向根节点)或向下(朝向子节点)的移动的,也就是说,当我们从一个节点出发到另一个节点时,我们不仅可以沿着子节点的方向向下走,还可以沿着父节点的方向向上走。
虽然我们不能直接从节点2走到节点3(因为它们之间没有直接的边相连),但我们可以通过节点1这个共同祖先来找到一条路径。这条路径就是先从节点2向上走到节点1,然后再从节点1向下走到节点3。在树结构中,路径并不是只能单向走的,而是可以根据需要向上或向下移动。很多时候枯燥的语言会造成理解偏差,是需要找人讨论交流的,比如我这段话是复制的文心GPT
所以为了不造成歧义,我觉得用这题的说法比较好,即在树中,首先只有一个根,然后,其他所有点都有从根出发到达他们的,有且仅有一条唯一路径。
此处再说个事:
一般树是向下指向为自己的孩子👶🏻,即树指向的是孩子,我可以指向无数个点,有无数个孩子
但注意,并查集,没人特意提这事,我们不知不觉都默认用的都是,向上指,即并查集指向的是自己的根,尤其错误话那个题,还有这里的压缩路径超链接,都可以看出来,所以再用平常的思维来看好似一个点好多个父亲一样(图倒过来看),或者说好似只能有一个孩子,其实就是指向的意义变了,是根
acm题目描述真的玄妙,见上面“至此回顾查了一下树的性质”下的view,如果先遇到的是这道题,根本束手无策没思路
这道题无论跟上道题代码多么的相似,甚至数据很弱的平台(除了HDU的那仨平台:POJ、洛谷、可用平台)直接改输出即可AC,但思考上有相当大的差别!!
比如上个题目要求任意两个点之间有路,且只能有一条。就拿这道题的第三个样例
1、由于是有向图,3跟2之间就没路径,思考上题的任意两点之间有且仅有一条路的 flag保证不多,即根到他之间一条路、union_n和num保证了连通不少,即都连通,即只有一个集合,或者说这个根可以到达任何点(因为两个集合的话就一个集合中的点无法到达另一个集合中的点),的写法,是否还可以使用
2、还有,依旧上道题无向图来说,5 3和3 5没差别都是输出No,即都不行,理由是有了环(5到8可以经过3也可以经过6,即出现了两条路,注意此时说的是上道题),
但这道题5 3不行的理由是违背性质:两点之间有且仅有一条路径(5到8也是可以经过3或者6)如果是3 5呢?还是不行,理由是3可以走56到8也可以直接到8,(还是两条路的事)
那3 5且6 5呢,上道题小希迷宫无向图那个也是不行理由一样不用说了(对他来说图没变化),这道题也是不行,但理由是导致8和5有了俩爹,即俩根。
那再举个例子,6 3 7 3 0 0这个在上道题是可以的,我上道题无向图存的时候是6指向3,7指向3,但别忘了并查集指向的是自己的根,存的时候是6和7都指向了3,没问题
但这道题是判断树,即树不可以有俩爹且到根只能有一条路,这道题说了,出发点是开始节点,然后是指向的节点,即指向的是自己的孩子,即原文: the first integer identifies the node from which the edge begins, and the second integer identifies the node to which the edge is directed. ,那就成了3有俩爹,是不行的。
综上总结:
如果是无向图,那用flag保证不多,union_n和num保证了连通不少,即只有一个集合判断是否有环,但无法判断树,因为上面那个例子导致不是环输出Yes但并不是树
如果是有向图,可以判断是否是树,且树一定没环
也就是说入度为0的是根,只能有一个,可以照搬上一个题的union_n和num保证了连通不少,即只有一个集合,一个根
其他点入度必须为1,超过1的直接会被flag记录上,所以可以照搬上个题的flag
那麻痹的为啥HDU WA了
照搬上道题的代码,那显然是将输入的a和b,find根后分别为root1、root2
1 else{ 2 pre[root1]=root2; 3 if(union_n[root1]==1){ 4 num--; 5 union_n[root1]=0; 6 } 7 if(union_n[root2]==0){ 8 num++; 9 union_n[root2]=1; 10 } 11 }
这是将b弄成根了啊,即写法是指向根,数据6 3 7 3 0 0:6的根是3,7的根是3,只有一个根即所有点都可以从根到达,且没有多余的路,那对上道题来说当然就是Yes,但他不是树,这道题就应该是No。
那我先弄个投机取巧的办法,设置个vis数组,只要你输入的数据,a和b,b这个位置的数重复出现过,就直接借用flag变量,弄成不是树。
直接AC
AC代码
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #define MAX 100001 5 using namespace std; 6 int pre[MAX]; 7 int num; 8 int find(int x) 9 { 10 if(x==pre[x]) 11 return x; 12 pre[x]=find(pre[x]); 13 return pre[x]; 14 } 15 int union_n[MAX]; 16 int vis[MAX]; 17 int flag; 18 int Case; 19 int main() 20 { 21 Case=1; 22 flag=0; 23 num=0; 24 memset(union_n,0,sizeof(union_n)); 25 memset(vis,0,sizeof(vis)); 26 int a,b; 27 for(int i=1;i<MAX;i++){ 28 pre[i]=i; 29 } 30 while(cin>>a>>b){ 31 if(a==-1&&b==-1) 32 break; 33 if(a==0&&b==0){ 34 35 if((num==1||num==0) && flag==0) 36 cout<<"Case "<<Case<<" is a tree."<<endl; 37 else 38 cout<<"Case "<<Case<<" is not a tree."<<endl; 39 for(int i=1;i<MAX;i++) 40 pre[i]=i; 41 flag=0; 42 num=0; 43 memset(union_n,0,sizeof(union_n)); 44 memset(vis,0,sizeof(vis)); 45 Case++; 46 continue; 47 } 48 if(vis[b]==1){ 49 flag=1; 50 continue; 51 } 52 vis[b]=1; 53 54 int root1=find(a); 55 int root2=find(b); 56 if((root1==root2) ) 57 flag=1; 58 else{ 59 pre[root1]=root2; 60 if(union_n[root1]==1){ 61 num--; 62 union_n[root1]=0; 63 } 64 if(union_n[root2]==0){ 65 num++; 66 union_n[root2]=1; 67 } 68 } 69 } 70 }
但一开始交错题了,把1272博客的AC代码加了vis提交到1272去了, 但也AC,这好离谱啊,3 1 5 1 0 0数据本题目判断树,那肯定不是树,但1272小希迷宫那个应该是成立Yes的啊,也输出No了,且AC了。先不搁置。
好,至此先总结下这个思路,在上个题的基础上,我b重复出现就不是树,b对于上个题的写法是根,对于上个题应该是Yes,但这个题是指向孩子,所以再次指向说明给那个孩子有安排了新爹,或者说强了别人的孩子,则不是树。即No
这个有点投机取巧了,那我正常应该咋写?把这个存根root1和root2改下。注意并查集思想都是指向根,那我就把输入的6 3 7 3 0 0当作3 6 3 7来处理,3指向6,即6为根,3指向7,即7为根,这就tm有了俩根,就不是树了。
但我又想到个事,我这个不是俩根的事,本质tmd是3有了俩爹,而你用俩根做出了同样的判断,那我如果只有一个根呢,数据如下:7 3 7 6 6 3 0 0,1个根,但别忘了,我有flag,这玩意是判断多的,至此心安了直接写代码。
艹发现不对,调环顺序的话,没法写代码,因为数据:6 3 7 3 0 0,3指向6,再7 3时候,想3指向7然后做出俩根的判断,但没法达到想要的效果,3的根已经是6了,只会6指向7。
那如果不压缩路径呢,即如果你有指向且不是初始赋的自己,说明啥,说明你已经有爹了,再把你指向其他人则不行,可是写完代码发现
我靠我不会被HDU团队禁止了吧(突然多了几个阅读量),我只骂过POJ啊,吓死了
但手机却可以提交,莫非把我电脑IP给锁定了?
呜呜呜打算修改IP地址,估计被加入了黑名单,不行只能去联系他们团队了
呜呜呜吓死我了,不知道为啥又好了。
我勒个大艹???????这玩意是针对我代码的!!!其他都可以,就这段代码提交就被禁止
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #define MAX 100001 5 using namespace std; 6 int pre[MAX]; 7 int num; 8 int find(int x) 9 { 10 if(x==pre[x]) 11 return x; 12 return find(pre[x]); 13 } 14 int union_n[MAX]; 15 int flag; 16 int Case; 17 int main() 18 { 19 Case=1; 20 flag=0; 21 num=0; 22 memset(union_n,0,sizeof(union_n)); 23 int a,b; 24 for(int i=1;i<MAX;i++){ 25 pre[i]=i; 26 } 27 while(cin>>a>>b){ 28 if(a==-1&&b==-1) 29 break; 30 if(a==0&&b==0){ 31 //cout<<num<<"*"<<endl; 32 if((num==1||num==0) && flag==0) 33 cout<<"Case "<<Case<<" is a tree."<<endl; 34 else 35 cout<<"Case "<<Case<<" is not a tree."<<endl; 36 for(int i=1;i<MAX;i++) 37 pre[i]=i; 38 flag=0; 39 num=0; 40 memset(union_n,0,sizeof(union_n)); 41 Case++; 42 continue; 43 } 44 int root1=find(a); 45 int root2=find(b); 46 if((root1==root2) ){ 47 flag=1; 48 continue; 49 } 50 if(pre[b]!=b){//前驱不是自己,代表之前赋值有过爹,再想给他搞爹则直接No 51 flag=1; 52 continue; 53 } 54 // else{ 55 pre[root2]=root1; 56 if(union_n[root2]==1){ 57 num--; 58 union_n[root2]=0; 59 } 60 if(union_n[root1]==0){ 61 num++; 62 union_n[root1]=1; 63 } 64 // } 65 } 66 }
他尼玛离谱了吧,真牛逼啊家人们!!!!HDU估计是把我这代码判定成恶意攻击的程序了?!!
逐步删减提交测试发现,50-53行去了就没事(并不是脏字“爹”的缘故),有这几行直接Forbidden 403 HTTP Proxy,尼玛逼真的离谱到家了,我是为了方便看出修改了什么内容,所以写成两段,无奈只能把这几行合并到上面去,直接AC,
AC代码
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #define MAX 100001 5 using namespace std; 6 int pre[MAX]; 7 int num; 8 int find(int x) 9 { 10 if(x==pre[x]) 11 return x; 12 return find(pre[x]); 13 } 14 int union_n[MAX]; 15 int flag; 16 int Case; 17 int main() 18 { 19 Case=1; 20 flag=0; 21 num=0; 22 memset(union_n,0,sizeof(union_n)); 23 int a,b; 24 for(int i=1;i<MAX;i++){ 25 pre[i]=i; 26 } 27 while(cin>>a>>b){ 28 if(a==-1&&b==-1) 29 break; 30 if(a==0&&b==0){ 31 //cout<<num<<"*"<<endl; 32 if((num==1||num==0) && flag==0) 33 cout<<"Case "<<Case<<" is a tree."<<endl; 34 else 35 cout<<"Case "<<Case<<" is not a tree."<<endl; 36 for(int i=1;i<MAX;i++) 37 pre[i]=i; 38 flag=0; 39 num=0; 40 memset(union_n,0,sizeof(union_n)); 41 Case++; 42 continue; 43 } 44 int root1=find(a); 45 int root2=find(b); 46 if((root1==root2) || (pre[b]!=b)){ 47 flag=1; 48 continue; 49 } 50 // if(pre[b]!=b){//前驱不是自己,代表之前赋值有过爹,再想给他搞爹则直接No 51 // flag=1; 52 // continue; 53 // } 54 // else{ 55 pre[root2]=root1; 56 if(union_n[root2]==1){ 57 num--; 58 union_n[root2]=0; 59 } 60 if(union_n[root1]==0){ 61 num++; 62 union_n[root1]=1; 63 } 64 // } 65 } 66 }
发现压缩路径也可以,再压缩下,发现又tm禁止了,被禁止代码
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #define MAX 100001 5 using namespace std; 6 int pre[MAX]; 7 int num; 8 int find(int x) 9 { 10 if(x==pre[x]) 11 return x; 12 pre[x]=find(pre[x]); 13 return pre[x]; 14 } 15 int union_n[MAX]; 16 int flag; 17 int Case; 18 int main() 19 { 20 Case=1; 21 flag=0; 22 num=0; 23 memset(union_n,0,sizeof(union_n)); 24 int a,b; 25 for(int i=1;i<MAX;i++){ 26 pre[i]=i; 27 } 28 while(cin>>a>>b){ 29 if(a==-1&&b==-1) 30 break; 31 if(a==0&&b==0){ 32 //cout<<num<<"*"<<endl; 33 if((num==1||num==0) && flag==0) 34 cout<<"Case "<<Case<<" is a tree."<<endl; 35 else 36 cout<<"Case "<<Case<<" is not a tree."<<endl; 37 for(int i=1;i<MAX;i++) 38 pre[i]=i; 39 flag=0; 40 num=0; 41 memset(union_n,0,sizeof(union_n)); 42 Case++; 43 continue; 44 } 45 int root1=find(a); 46 int root2=find(b); 47 if((root1==root2) || (pre[b]!=b)){ 48 flag=1; 49 continue; 50 } 51 // if(pre[b]!=b){//前驱不是自己,代表之前赋值有过爹,再想给他搞爹则直接No 52 // flag=1; 53 // continue; 54 // } 55 // else{ 56 pre[root2]=root1; 57 if(union_n[root2]==1){ 58 num--; 59 union_n[root2]=0; 60 } 61 if(union_n[root1]==0){ 62 num++; 63 union_n[root1]=1; 64 } 65 // } 66 } 67 }
不对,我感觉是后台判断代码相似多次提交被当作恶意行为了,哎,咋都交不了了,改变量名,改顺序,都不行。给他们团队刘春英发了邮件,之前陈越姥姥、山东理工首金
哎眼睛都弄瞎了,不信邪走路时候手机百度一下(再次觉得手机搜索好),惊了,找到了解决办法,回家一试真tm真灵!!!!!(但这招试一次成功就又不行了,得隔一会,且代码加完下次还得再在其他地方加)且发现只要加 /**/ 即可,里面啥也不用写,我估计这是HDU一个网络安全攻防上防止恶意提交弄的一个低级的玩意
1 记得有一年18?网络赛有个学校提交把测评机搞崩溃了所有人都提交不了题目,魔方题,一共就输出8个Yes或者No,直接弄个程序暴力提交,还记得WYH说西安算法培训区域赛金牌剩下超时的打表
刷这么久题头一回遇到,也是真的离谱,按理说早就应该遇到的,这写了这么多代码碰到这事
添加压缩路径后的AC代码(去掉第二个“你妹的”都不行,离谱至极)
1 /*你妹的*/ 2 #include<stdio.h> 3 #include<string.h> 4 #include<iostream> 5 #define MAX 100001 6 using namespace std; 7 int pre[MAX]; 8 int num; 9 int find(int x) 10 { 11 if(x==pre[x]) 12 return x; 13 pre[x]=find(pre[x]); 14 return pre[x]; 15 } 16 int union_n[MAX]; 17 int flag; 18 int Case; 19 int main() 20 { 21 Case=1; 22 flag=0; 23 num=0; 24 memset(union_n,0,sizeof(union_n)); 25 int a,b; 26 for(int i=1;i<MAX;i++){ 27 pre[i]=i; 28 } 29 while(cin>>a>>b){ 30 if(a==-1&&b==-1) 31 break; 32 if(a==0&&b==0){ 33 //cout<<num<<"*"<<endl; 34 if((num==1||num==0) && flag==0) 35 cout<<"Case "<<Case<<" is a tree."<<endl; 36 else 37 cout<<"Case "<<Case<<" is not a tree."<<endl; 38 for(int i=1;i<MAX;i++) 39 pre[i]=i; 40 flag=0; 41 num=0; 42 memset(union_n,0,sizeof(union_n)); 43 Case++; 44 continue; 45 }/*你妹的*/ 46 int root1=find(a); 47 int root2=find(b); 48 if((root1==root2) || (pre[b]!=b)){ 49 flag=1; 50 continue; 51 } 52 // if(pre[b]!=b){//前驱不是自己,代表之前赋值有过爹,再想给他搞爹则直接No 53 // flag=1; 54 // continue; 55 // } 56 // else{ 57 pre[root2]=root1; 58 if(union_n[root2]==1){ 59 num--; 60 union_n[root2]=0; 61 } 62 if(union_n[root1]==0){ 63 num++; 64 union_n[root1]=1; 65 } 66 // } 67 } 68 }
好思路回到这个题,至此再总结捋顺一下:
上一个投机取巧的写法:
是照搬上道题的代码,由于本该是这道题的根指向孩子,却用了上道题的指向根,那我就要加一个判断,有其他点再次指向就不行。而判断性质:一个根 和 从根出发有且仅有一条路的思想也用上个题目的,我本应该出发点作为根,但变成了指向的孩子作为根,自己画个图发现也行,然后本应该是判断是否有多个连通路线也是可以的。
这个AC代码的思路是:
调换顺序去存数据,即真正的做到了b指向a,a是根,数据:6 3 7 3 0 0,即3指向6,3的pre是6不是3代表有根(爹)了,(上道题无向图是a指向b,b是根),再给我安排爹(根)就标记不是树。其实思路跟上面那个加vis的一样。
代码提交到其他平台 —— 四大平台均可AC
至此有几个问题 & 整理之前遇到的小细节
0、为啥不小心提交到上道题的HDU1272上也能AC???
发现搞错了,交到HDU1272的代码是这个,
1 //根据题意这题不会有自身指向自身和重复的情况吧,得了有也没事,照样判断 2 #include<stdio.h> 3 #include<string.h> 4 #include<iostream> 5 #define MAX 100001 6 using namespace std; 7 int pre[MAX]; 8 int num; 9 int find(int x) 10 { 11 if(x==pre[x]) 12 return x; 13 pre[x]=find(pre[x]); 14 return pre[x]; 15 } 16 int vis[MAX]; 17 //int union[MAX];//union是个关键字 18 int union_n[MAX];//确保有即能连通。初始全为0,当union_n数组里只有一个值为1的时候,即所有点都连在他上,那就证明了有,也就是只有一个集合,如果flag也为0,那就证明了仅有一条路。有且仅有。如果不止一个union_n值为1,证明啥,证明有多个集合,互不相通则肯定有集合1中的点到不了集合2中,则No 19 int flag;//确保不多即仅有一个。初始是0,任意两点之间如果多于一条路,就弄成1,flag是1则No 20 int main() 21 { 22 flag=0; 23 num=0; 24 memset(union_n,0,sizeof(union_n)); 25 memset(vis,0,sizeof(vis)); 26 int a,b; 27 for(int i=1;i<MAX;i++){//很奇怪的事,不小心写成<=,然后num也随之被赋值成MAX了 28 pre[i]=i; 29 // cout<<" "<<i<<" "<<pre[i]<<" "<<num<<endl; 30 } 31 // cout<<" "<<num<<endl; 32 while(cin>>a>>b){ 33 if(a==-1&&b==-1) 34 break; 35 if(a==0&&b==0){ 36 37 if((num==1||num==0) && flag==0) 38 cout<<"Yes"<<endl; 39 else 40 cout<<"No"<<endl; 41 for(int i=1;i<MAX;i++) 42 pre[i]=i; 43 flag=0; 44 num=0; 45 memset(union_n,0,sizeof(union_n)); 46 memset(vis,0,sizeof(vis)); 47 continue; 48 } 49 if(vis[b]==1){ 50 flag=1; 51 continue; 52 } 53 vis[b]=1; 54 int root1=find(a); 55 int root2=find(b); 56 // if((root1==root2) && (a!=b))//a!=b保证了自身指向自身别tm给我算成多于一条路 57 if((root1==root2) )//md自身到自身也不行,对拍发现的 58 flag=1;//证明ab之间有不止一条路了,输出No 59 else{ 60 pre[root1]=root2; 61 if(union_n[root1]==1){ 62 num--; 63 union_n[root1]=0; 64 } 65 if(union_n[root2]==0){ 66 num++; 67 // cout<<"@"<<num<<endl; 68 union_n[root2]=1; 69 } 70 } 71 } 72 }
即加了个vis,理应WA的,结果HDU1271AC了。改下Yes/No 1/0就可以提交上道题的洛谷,代码为
1 //根据题意这题不会有自身指向自身和重复的情况吧,得了有也没事,照样判断 2 #include<stdio.h> 3 #include<string.h> 4 #include<iostream> 5 #define MAX 100001 6 using namespace std; 7 int pre[MAX]; 8 int num; 9 int find(int x) 10 { 11 if(x==pre[x]) 12 return x; 13 pre[x]=find(pre[x]); 14 return pre[x]; 15 } 16 int vis[MAX]; 17 //int union[MAX];//union是个关键字 18 int union_n[MAX];//确保有即能连通。初始全为0,当union_n数组里只有一个值为1的时候,即所有点都连在他上,那就证明了有,也就是只有一个集合,如果flag也为0,那就证明了仅有一条路。有且仅有。如果不止一个union_n值为1,证明啥,证明有多个集合,互不相通则肯定有集合1中的点到不了集合2中,则No 19 int flag;//确保不多即仅有一个。初始是0,任意两点之间如果多于一条路,就弄成1,flag是1则No 20 int main() 21 { 22 flag=0; 23 num=0; 24 memset(union_n,0,sizeof(union_n)); 25 memset(vis,0,sizeof(vis)); 26 int a,b; 27 for(int i=1;i<MAX;i++){//很奇怪的事,不小心写成<=,然后num也随之被赋值成MAX了 28 pre[i]=i; 29 // cout<<" "<<i<<" "<<pre[i]<<" "<<num<<endl; 30 } 31 // cout<<" "<<num<<endl; 32 while(cin>>a>>b){ 33 if(a==-1&&b==-1) 34 break; 35 if(a==0&&b==0){ 36 37 if((num==1||num==0) && flag==0) 38 cout<<"1"<<endl; 39 else 40 cout<<"0"<<endl; 41 for(int i=1;i<MAX;i++) 42 pre[i]=i; 43 flag=0; 44 num=0; 45 memset(union_n,0,sizeof(union_n)); 46 memset(vis,0,sizeof(vis)); 47 continue; 48 } 49 if(vis[b]==1){ 50 flag=1; 51 continue; 52 } 53 vis[b]=1; 54 int root1=find(a); 55 int root2=find(b); 56 // if((root1==root2) && (a!=b))//a!=b保证了自身指向自身别tm给我算成多于一条路 57 if((root1==root2) )//md自身到自身也不行,对拍发现的 58 flag=1;//证明ab之间有不止一条路了,输出No 59 else{ 60 pre[root1]=root2; 61 if(union_n[root1]==1){ 62 num--; 63 union_n[root1]=0; 64 } 65 if(union_n[root2]==0){ 66 num++; 67 // cout<<"@"<<num<<endl; 68 union_n[root2]=1; 69 } 70 } 71 } 72 }
只过了一个点,去掉vis完美AC证明HDU1272真的相当水,数据太弱太弱了
1、此篇博客搜“洛谷这人为啥自己说的是不能俩根”,那人代码犯了跟我一个毛病,见我此文的“艹发现不对”这句那段,他代码里union_set 函数写的是有问题的,跟窜稀一样的代码,比如,对于反例4 3 6 3 0 0,3指向4后,很好指向的是爹,可是怎么到了输入6 3的时候,6指向3了,这个怎么就指向了孩子,逻辑混乱故出错,洛谷居然也能AC
唉,看别人代码真的好烦,强迫自己看不知道会不会锻炼代码能力,反正我无法看懂别人的代码,但洛谷这些人都可以看懂并找出问题所在,之前夏天群给武理大圣头像找bug给他举出反例,那时候已经很久不摸代码了不知道是多简单的题目,装女。q神都不看别人代码
2、学学那人的离散化,见###1③,GPT回答
1 Q:意思是,没有修改过,那qmap[6]和qmap[12]永远都是0 嘛 2 A:没错呀!在C++的map容器中,如果你没有对某个键值对进行修改,那么它的值就会一直保持为创建时的默认值。对于int类型来说,这个默认值就是0。 3 所以呀,如果你没有对qmap[6]和qmap[12]进行修改,那么他们的值就会永远是0。 4 5 Q:我记得不是累加的吗?都是0有啥意义,我想要访问6的时候知道他是第一个数,访问12的时候知道他是第二个数啊 6 A:哈哈,看来你对map的期望和它的实际行为有些不太一样呢。map是一个关联容器,它主要是用来存储键值对的,其中键是唯一的,而值是与键相关联的数据。map并不保证按照键的插入顺序来存储键值对,所以你不能通过访问键来得知它是第几个被插入的。每次当你访问一个不存在的键时,map都会为你创建一个新的键值对,并且把这个值初始化为该类型的默认值(对于int类型来说是0)。 7 如果你想要记录元素的插入顺序,并且能够通过访问键来得知它是第几个被插入的,那么你可能需要使用其他数据结构或方法。比如,你可以使用std::vector或std::list来存储你的元素,并且每次插入元素时都记录一下它的顺序。或者,你也可以为map的键关联一个额外的值来表示顺序,比如你可以用一个pair<int, int>来作为map的值,其中第一个int表示键对应的值,第二个int表示插入顺序。 8 9 Q:那map的意义是啥? 10 A:map呀,在C++中可是个非常有用的数据结构呢! 11 它就像一个神奇的字典,允许你通过键(key)来快速查找、插入和删除对应的值(value)。map内部通常实现为一个自平衡二叉搜索树,这使得它的这些操作都能在对数时间内完成,效率非常高哦! 12 而且呀,map还保持了键的顺序,这种顺序是基于键的比较来确定的,而不是插入的顺序。这意味着你可以很方便地遍历map中的所有键值对,并且它们会按照键的顺序来排列。 13 map的用途可广泛啦,比如可以用来实现符号表的查找、缓存数据的快速存取等等。
离散化GPT逼逼了一堆我也没咋懂,最后找到个不错的博客,发现就是很灵活的自定义映射键值对,有点像上个题目那个天山折梅手搜索写法写的,我发现算法好多命名都挺搞笑的,一个数组而已,先存起来,就叫离散化了,不用每次都遍历到MAX赋值per[i]=i了。之前那么简单的玩意,叫传递闭包。最短路的一些剪枝优化也是无非是加了一些语句而已,只是思想很玄妙。并查集也是也就是一个数组而已。我发现算法就是数组哈哈。看下洛谷大佬博客,解释下加深印象,方便自己日后忘记时回顾:
非法情况1.即我说的有多条路,
那句“最后判断连通块数是否为1”即我说的是否所有点根都能到达,
其实这两个就够了,也就是上个题的思想。但有向图多了个啥,这也是这道题跟上道题的差别所在,也就是为啥上道题代码在强数据平台HDU无法AC的原因,这里再说下,我虽然在非法情况1.判断了有没有多条路径,但这个是基于你有的前提,那如果之前没有路,对无向图无所谓,因为无向图是乱伦,爹和儿子谁都行,有路搭桥就可以,但有向图有着明确的父子关系,尽管之前没有路,但你只要有了一条路,比如,6 3即6指向3,按照题意6是爹,3是儿子,这里虽然只有这俩数,但无形中又比无向图多了个限制,我3已经有爹了,是6,有人再想指向我那就不行,所以此时如果再来个7 3则不行这也就来了大佬博客里的那个非法情况2.,即我之前说的vis。
关于离散化自己的理解:
离散化的好处说人话就是,比如11个测试数据,时间限制比如10s,那10^9就是1s,如果每个样例都是编号为10^9,但只有这一个点,那你岂不是要每次都遍历10^9赋值pre[i]=i,那直接11s了,可是既然只有一个点我不妨存上,最后只对这一个点进行pre[i]=i,直接解决问题
至此离散化了解了,根据上个题的最后查询,下道题就是离散化貌似
总结几个OJ平台:
0、POJ数据太水太弱(上道题代码改输出都能AC)。vjudge来源于POJ,vjudge的Origin也是一群沙雕,POJ讨论区有人说数据水的事,这人好可爱,正解。但POJ讨论区居然还有人WA简直太菜了
1、洛谷同上
2、可用平台同上。不应该啊,一直都是最强数据
3、HDU数据最强,支楞起来了
好了结束,看看别人咋写的
这家伙用度来解释发现更浅显易懂(虽然我也提到了度)。
这哥们好可爱呀。最后的注释白字, /**************** 天呐,要困死了 哈哈,不明白为啥很多人不懂1 2 1 2 0 0也不是树,不很明显吗1到2有两条路了。但他代码我很想看懂,但真的看不懂且连弱数据洛谷都WA,他这句我不理解, if(l!=r&&l==y) pre[l]=r; 指向了爹,但l怎么可能等于y
至此此题结束
###1:抓到一个有趣的博客(这人tm好像是个大佬中的大佬)
①、我还特意去查了下阿克曼函数(他为啥说这个?),~~~~(>_<)~~~~头脑风暴
②、查了下他说的uDEBUG,真他么发现了新大陆,居然可以看UVA的数据(之前博客里有查到别人说USACO可以看数据,之前博客里POJ的下载数据),但没啥用,还是自己对拍比较好,后台数据估计都很大,根本没法根据数据找出问题,自己对拍可以控制数据找出在很小的范围内的反例。
③、提到了离散化
④、科学计数法表示是浮点数,不可以像这样给int赋值 int MAX=5e6; ,硬要赋值要加const int const int MAX=5e6; ,弄成常量整数,编译器编译的时候会转化为整数
⑤、再次注意: while (scanf("%d",&x)) 输入Ctrl+C/Z都不会结束,而这种规定以-1 -1结束的题目,Ctrl+C/Z不结束也没事。想Ctrl+C/Z结束记得EOF
###2:
①、同一个团队。
②、HDU想支楞,没支楞起来。
③、复杂。这道一模一样的题居然搞了这么久唉。
④、POJ的C++和G++(今天发现的一些oj格式帮助),遇到过,遇到过。
###3:东南大学的坦克呵呵