POJ 8道简单的并查集 秒光了所有已知的简单并查集,总结一下,共8道。

并查集算是自己掌握的第一个进阶数据结构或算法。

 

很简单的结构,解决问题的针对性也很强。

 

是并查集的问题也许一眼就能看出来。

 

一口气A了8道。

 

今天先把代码贴上来吧。

 

有时间写个总结。

 

坐了一会了,有点累。。

 

第一道1182,经典的食物链,是并查集的一个典型模型,但是并不是最简模型。

 

代码:

#include <iostream> using namespace std; struct { int kind; int parent; }animal[50010]; void MakeSet(int SizeOfSet) { //初始化并查集,将每个结点的根结点设置为自己 //相互之间关系确定的结点才放入一个集合里 //一句话都没有输入之前所有节点之间的关系都不确定,所以各自单独一个集合 //每个结合的根节点kind都是0,有利于合并时kind值的计算 for (int i = 1;i <= SizeOfSet;i++) { animal[i].parent = i; animal[i].kind = 0; } } void Union(int RootOfX,int RootOfY,int NodeX,int NodeY,int D) { //此函数作用:1.将Y的根节点的根节点设置为X的根结点 //2.由于设置后RootOfY已经不再是根节点,所以要保证其kind相对于RootOfX的正确性 //在此函数里,D==0说明NodeX与NodeY同类, //D==1说明NodeX吃NodeY,因此传参前要将D减一 //将Y所在树依附到X所在树上 animal[RootOfY].parent = RootOfX; //更新RootOfY的kind,保证其kind相对于RootOfX的正确性 //原始公式为animal[NodeX].kind-(animal[NodeY].kind+amimal[RootOfY].kind)=D; animal[RootOfY].kind = (-D+(animal[NodeX].kind-animal[NodeY].kind)+3)%3; } int Find(int NodeToFind) { //此函数作用:1找到NodeToFind所在集合,即找到其根节点 //2.查找的过程是一个递归过程,递归出口是遇到一个根节点为自身的结点,即当前集合的根节点 //然后递归返回的路径上依次将各个各个结点的根节点设置为此节点,并继续返回此根结点,这样就可以 //把集合中所有结点的根节点设置为同一个根结点,这叫做“路径压缩”, //是为了使并查集稳定而做的一种改进,目的是避免并查集成为接近于链表的结构,因为并查集的优势体现在 //树的深度较浅,查找容易,此举可看作对并查集深度的控制。 //递归返回路径上,除了要做更新途径结点(按照距根从近到远的顺序)的根结点外,还要依次修正 //途径结点的kind。这是因为在Union操作中只是保证了直接和根节点相连的 //结点(即未作Union操作前的某一树的根)kind的正确性,其他节点kind的正确性就需要在这里修正 //从近到远进行修正恰好保证了每次修正都有理有据。 //每次修正都要依仗其原根节点kind的正确性,因为这是一个相对计算的关系 if(animal[NodeToFind].parent==NodeToFind) return NodeToFind; int temp = animal[NodeToFind].parent; animal[NodeToFind].parent = Find(animal[NodeToFind].parent); //更新NodeToFind结点kind的正确性,因为原来的kind是相对于0(根节点kind都为0), //原来的根节点现在已经不是根节点了,所以只需要根据原根节点现有的kind值即可更新 animal[NodeToFind].kind = (animal[NodeToFind].kind+animal[temp].kind+3)%3; return animal[NodeToFind].parent; } int main() { int n,k; cin>>n>>k; MakeSet(n); int x,y,d; int NumOfLies = 0; while (k--) { //cin>>d>>x>>y; //cin导致TLE scanf("%d%d%d",&d,&x,&y); if(x>n||y>n) NumOfLies++; else if(x==y&&d==2) NumOfLies++; else { int rootx = Find(x); int rooty = Find(y); if (rootx==rooty) { //如果两节点的根相等,说明根据以往结论,这两点的关系已经确定,可以开始判断 if(d==1&&animal[x].kind!=animal[y].kind) NumOfLies++; if(d==2&&(animal[x].kind-animal[y].kind+3)%3!=1) NumOfLies++; } else { //x和y不在一个集合里,说明两者关系尚未确定,则将两者关系进行确定, //即将两者所在集合进行合并操作Union,Union操作只保证了和根结点相连的结点kind //的正确性,不要担心,由于并查集的特点(根节点表示法的树)所以无法从根向下找到 //子节点,因此子节点即使有错误也是安全的,到了需要访问(Find)的时候再修正也不迟 Union(rootx,rooty,x,y,d-1); //别忘了d要减一才符合我们规定的含义 } } } cout<<NumOfLies<<endl; }

 

第二道1703“抓帮派分子”,并查集的最简模型了吧。

代码:

#include <iostream> #include <stdio.h> using namespace std; struct _cri{ int parent; int gang; //0 for the same as SetRoot,1 for different from it }cri[100010]; void MakeSet(int SizeOfSet) { for (int i = 1;i <= SizeOfSet;i++) { cri[i].parent = i; cri[i].gang = 0; } } void Union(int RootOfX,int RootOfY,int NodeX,int NodeY,int SorD) { cri[RootOfY].parent = RootOfX; cri[RootOfY].gang = cri[NodeX].gang==cri[NodeY].gang?SorD:(1-SorD); } int Find(int NodeToFind) { if(cri[NodeToFind].parent==NodeToFind) return NodeToFind; int temp = cri[NodeToFind].parent; cri[NodeToFind].parent = Find(cri[NodeToFind].parent); cri[NodeToFind].gang = cri[temp].gang==0?cri[NodeToFind].gang:(1-cri[NodeToFind].gang); return cri[NodeToFind].parent; } int main() { int NumOfCases; cin>>NumOfCases; while (NumOfCases--) { int N,M; char AorD; int cri_a,cri_b; cin>>N>>M; MakeSet(N); while (M--) { scanf("/n%c%d%d",&AorD,&cri_a,&cri_b); int rootx =Find(cri_a); int rooty = Find(cri_b); if (AorD=='D') Union(rootx,rooty,cri_a,cri_b,1); else { if (rootx==rooty) { if (cri[cri_a].gang==cri[cri_b].gang) cout<<"In the same gang."<<endl; else cout<<"In different gangs."<<endl; } else cout<<"Not sure yet."<<endl; } } } }

 

第三道1988 这道题一开始没什么头绪 后来琢磨了一下 豁然开朗 一开始我设计的并查集节点中数据量还较多 AC了之后发现有两个数据是多余的,精简了之后 就又成了经典的并查集 一如食物链

代码:

#include <iostream> using namespace std; struct { int parent; int CubeInStack; int CubeBelow; }cube[30010]; void MakeSet(int SizeOfSet) { for (int i = 1;i <= SizeOfSet;i++) { cube[i].parent = i; cube[i].CubeInStack = 1; cube[i].CubeBelow = 0; } } void Union(int RootOfX,int RootOfY,int NodeX,int NodeY) { //将Y所在集合依附于X所在集合,更新X集合根节点的CubeInStack,更新Y集合根节点的CubeBelow cube[RootOfX].parent = RootOfY; cube[RootOfX].CubeBelow += cube[RootOfY].CubeInStack; cube[RootOfY].CubeInStack += cube[RootOfX].CubeInStack; } int Find(int NodeToFind) { if(cube[NodeToFind].parent==NodeToFind) return NodeToFind; int temp = cube[NodeToFind].parent; cube[NodeToFind].parent = Find(cube[NodeToFind].parent); cube[NodeToFind].CubeBelow += cube[temp].CubeBelow; return cube[NodeToFind].parent; } int main() { MakeSet(30005); int NumOfOrders; cin>>NumOfOrders; char order; int x,y; while(NumOfOrders--) { //scanf("%c %d %d",&order,&x,&y); scanf("/n%c",&order); //cin>>order; if (order=='M') { scanf(" %d %d",&x,&y); //cin>>x>>y; int rootx = Find(x); int rooty = Find(y); Union(rootx,rooty,x,y); } else { scanf(" %d",&x); Find(x); //cin>>x; cout<<cube[x].CubeBelow<<endl; } //getchar(); } }

 

第四道1308 Is it a tree 判断一个图是不是树 这道题的题意简直是紧贴并查集的定义 一开始我觉得用模拟也能做 后来发现有一种情况不能辨别 就是一棵树加一个环的情况 用并查集则可避免此问题 代码如下:

#include <iostream> using namespace std; struct{ int from; int to; int parent; int used; }theNode[1005]; void MakeSet() { for (int i = 1;i <= 1000;i++) { theNode[i].from=0; theNode[i].to=0; theNode[i].parent=i; theNode[i].used=0; } } void Add(int NodeX,int NodeY) { theNode[NodeX].used = 1; theNode[NodeY].used = 1; theNode[NodeX].to = NodeY; theNode[NodeY].from = NodeX; } int FindRoot(int NodeToFind,int FindTimes) { if(FindTimes>1000) //it is a loop return -5; if(theNode[NodeToFind].from==0) return NodeToFind; return FindRoot(theNode[NodeToFind].from,FindTimes+1); } int main() { int Case=0; while (++Case) { MakeSet(); bool IsATree=true; int a,b; while (cin>>a>>b&&a>0) { if(a==b) IsATree=false; else if(theNode[b].from==0) Add(a,b); else IsATree=false; } if (a==0) { if (!IsATree) { cout<<"Case "<<Case<<" is not a tree."<<endl; } else { int root=0,times=0; for (int i = 1;i < 1000;i++) { if (theNode[i].used==1) { if(times==0) root=FindRoot(i,1); else if(root!=FindRoot(i,1)||FindRoot(i,1)<0||root<0) { IsATree=false; break; } times++; } } if (IsATree) { cout<<"Case "<<Case<<" is a tree."<<endl; } else { cout<<"Case "<<Case<<" is not a tree."<<endl; } } } else break; } }

 

第五道 2524 宗教信仰问题 这个是并查集的最简模型了 最简最简了 结点里只有一个PARENT就足够了 今天意识到。。。如果只有一个成员 那么何必定义结构体呢。。。直接上数组。。。

代码如下:

#include <iostream> using namespace std; struct { int parent; }student[50005]; void MakeSet(int SizeOfInt) { for (int i = 1;i <= SizeOfInt;i++) student[i].parent=i; } void Union(int RootOfX,int RootOfY,int NodeX,int NodeY) { student[RootOfY].parent = RootOfX; } int Find(int NodeToFind) { if(student[NodeToFind].parent==NodeToFind) return NodeToFind; student[NodeToFind].parent = Find(student[NodeToFind].parent); return student[NodeToFind].parent; } int main() { int Case = 1; int NumOfStudents,NumOfPairs; while (cin>>NumOfStudents>>NumOfPairs&&NumOfStudents!=0) { MakeSet(NumOfStudents+1); int temp = NumOfStudents; int x,y,rootx,rooty; while (NumOfPairs--) { scanf("/n%d %d",&x,&y);//cin>>x>>y; rootx=Find(x); rooty=Find(y); if(rootx!=rooty) { NumOfStudents--; Union(rootx,rooty,x,y); } } cout<<"Case "<<Case++<<": "<<NumOfStudents<<endl; } }

 

第六道2492 臭虫中的同性恋 不多说 相对于食物链的三性 两性问题比较简单

代码如下:

#include <iostream> using namespace std; struct { int parent; int value; //0 for same to the root,1 for different from the root. }Bug[2005]; void MakeSet(int SizeOfSet) { for (int i = 1;i <= SizeOfSet;i++) { Bug[i].parent=i; Bug[i].value=0; } } void Union(int RootOfX,int RootOfY,int BugX,int BugY) { Bug[RootOfY].parent = RootOfX; Bug[RootOfY].value = (Bug[BugX].value==Bug[BugY].value)?1:0; } int Find(int BugToFind) { if(Bug[BugToFind].parent==BugToFind) return BugToFind; int temp = Bug[BugToFind].parent; Bug[BugToFind].parent = Find(Bug[BugToFind].parent); Bug[BugToFind].value = Bug[temp].value==0?Bug[BugToFind].value:1-Bug[BugToFind].value; return Bug[BugToFind].parent; } int main() { int count; cin>>count; int Case = 1; while (count--) { int NumOfBugs,NumOfPairs; cin>>NumOfBugs>>NumOfPairs; MakeSet(NumOfBugs); int x,y,rootx,rooty; bool suspicious = false; while (NumOfPairs--) { scanf("/n%d %d",&x,&y); rootx = Find(x); rooty = Find(y); if(rootx!=rooty) Union(rootx,rooty,x,y); else { if(Bug[x].value==Bug[y].value) suspicious = true; } } if(suspicious) cout<<"Scenario #"<<Case++<<":"<<endl<<"Suspicious bugs found!"<<endl<<endl; else cout<<"Scenario #"<<Case++<<":"<<endl<<"No suspicious bugs found!"<<endl<<endl; } }

 

第七道 2236 无线网络 也动了点小脑筋才搞定 其实本质都是一样的 做多了就发现 就看你能不能从题目中分辨出那个最基本的模型

代码如下:

#include <iostream> using namespace std; struct { unsigned int x; unsigned int y; int parent; }Computer[1005]; void MakeSet(int SizeOfSet) { for (int i = 1;i <= SizeOfSet;i++) Computer[i].parent = i; } void Union(int RootX,int RootY) { Computer[RootY].parent = RootX; } int Find(int NodeToFind) { if(Computer[NodeToFind].parent==NodeToFind) return NodeToFind; Computer[NodeToFind].parent=Find(Computer[NodeToFind].parent); return Computer[NodeToFind].parent; } int main() { int NumOfCpt,Dis; cin>>NumOfCpt>>Dis; MakeSet(NumOfCpt); for (int i = 1;i <= NumOfCpt;i++) scanf("/n%d %d",&Computer[i].x,&Computer[i].y); char order; int a,b; int HasRepaired[1005],cur=0; while (scanf("/n%c %d",&order,&a)!=EOF) { int rootx,rooty; if(order=='O') { HasRepaired[cur++]=a; int DisBetween; for (int i = 0;i < cur;i++) { DisBetween = (Computer[HasRepaired[i]].x-Computer[a].x)*(Computer[HasRepaired[i]].x-Computer[a].x)+(Computer[HasRepaired[i]].y-Computer[a].y)*(Computer[HasRepaired[i]].y-Computer[a].y); if (DisBetween<=Dis*Dis) { rootx=Find(a); rooty=Find(HasRepaired[i]); if(rootx!=rooty) Union(rootx,rooty); } } } else { scanf(" %d",&b); rootx=Find(a); rooty=Find(b); if(rootx==rooty) cout<<"SUCCESS"<<endl; else cout<<"FAIL"<<endl; } } }

 

第八道 疑似患者。。。和当前形式很切题 一次AC 比较爽快

代码如下:

#include <iostream> using namespace std; struct { unsigned int x; unsigned int y; int parent; }Computer[1005]; void MakeSet(int SizeOfSet) { for (int i = 1;i <= SizeOfSet;i++) Computer[i].parent = i; } void Union(int RootX,int RootY) { Computer[RootY].parent = RootX; } int Find(int NodeToFind) { if(Computer[NodeToFind].parent==NodeToFind) return NodeToFind; Computer[NodeToFind].parent=Find(Computer[NodeToFind].parent); return Computer[NodeToFind].parent; } int main() { int NumOfCpt,Dis; cin>>NumOfCpt>>Dis; MakeSet(NumOfCpt); for (int i = 1;i <= NumOfCpt;i++) scanf("/n%d %d",&Computer[i].x,&Computer[i].y); char order; int a,b; int HasRepaired[1005],cur=0; while (scanf("/n%c %d",&order,&a)!=EOF) { int rootx,rooty; if(order=='O') { HasRepaired[cur++]=a; int DisBetween; for (int i = 0;i < cur;i++) { DisBetween = (Computer[HasRepaired[i]].x-Computer[a].x)*(Computer[HasRepaired[i]].x-Computer[a].x)+(Computer[HasRepaired[i]].y-Computer[a].y)*(Computer[HasRepaired[i]].y-Computer[a].y); if (DisBetween<=Dis*Dis) { rootx=Find(a); rooty=Find(HasRepaired[i]); if(rootx!=rooty) Union(rootx,rooty); } } } else { scanf(" %d",&b); rootx=Find(a); rooty=Find(b); if(rootx==rooty) cout<<"SUCCESS"<<endl; else cout<<"FAIL"<<endl; } } }

 

就这样

 

嗯,希望H1N1能得到控制,大家都平安,good night~

你可能感兴趣的:(数据结构,c,算法,网络,tree,bugs)