1182
今天接触了并查集,并查集在数据结构上很简单,虽说是树(二叉树都怕,更别说是树了),但是它是有根节点表示法来表示的,可以用数组模拟,相当的简单。但是用处有很大。
并查集主要用于这样的问题,问题中有大量数据,数据之间有简单的关系,而这种关系又带有简单的传递性。
如果按照矩阵的方式表示数据间的关系,即用d[i][j]表示i和j之间的关系,空间复杂度O(n^2)显然无法承受,这时就用到了并查集。
并查集的空间复杂度为O(n)。
并查集提供三个操作:
1。初始化
2。查找结点所在集合
3。合并两个集合
并查集以森林的形式存在,每个森里都代表一个集合,集合中的结点间存在某种关系,集合是由根节点进行标示的,即判断两个节点是否在一个集合里,只需看两节点所在集合的根结点是否相同即可。
理解尚浅,待多做几道并查集的题后,再来总结吧。
本题代码如下,注释很详细:
另外,本题cin的话会TLE,而scanf就可以AC,让我见识了scanf和cin的差距,也是第一次遇见输入量这么大的题 100000个输入
//1182 #include <iostream> using namespace std; struct { int kind; int parent; }animal[50010]; void MakeSet(int SizeOfSet) { //初始化并查集,将每个结点的根结点设置为自己 //相互之间关系确定的结点才放入一个集合里 //一句话都没有输入之前所有节点之间的关系都不确定,所以各自单独一个集合 //每个结合的根节点kind都是0,有利于合并时kind值的计算 for (int i = 0;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; }
这个解题报告很好:
http://www.cppblog.com/tortoisewu/archive/2009/07/14/85501.html
1703是1182的简化版
解题思路一样
代码如下:
#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; } } } }