http://acm.hdu.edu.cn/showproblem.php?pid=1811
思路不是自己的,这次纯粹当时学习了。
是一道拓扑排序题, 但是因为多了个等号,所以增加了点难度,这题的关键也在于怎样处理这个等号。相等的那些数,其实都是同一个数,所以需要先进行预处理,把所有相同的那些数,只用其中的一个来代表,所以,可以用并查集来把相同的数并成一颗树,之后都是用这个树根的数来代替这个树中所有的数。另外,注意去重。最后,就是对于处理好的数据使用拓扑排序。
因为有的点:a == b, 则用并查集把两者并入同一个集合,看成是一个整体(一个单位), 所有点以根结点建图。
邻接表实现的拓扑排序:
1.找出入度为零的顶点dot(根结点建图), 进入队列,并更新此时已经找到的入度为0节点的个数。
2.当队列有两个含两个以上的点时,记录flag为1,表示信息不完整。(因为这几个入度为0的点之间没有联系,排序的时候无法判断,而且可能剩下的图有环, 出现信息冲突,因此此处就算知道可能会信息不完整也不能马上return,要继续运行
3.判断是否有信息冲突)删除含有dot点的边, 减少与dot相联系的其他的点入度, 当其他点入度为零时, 进入队列。
4.重复步骤2和3,直到队列为空。
5.若入度为0点的个数和题目给出的点个数N不同,则表示有环,发生信息冲突。
6.若入度为0点的个数和题目给出的点个数N相同,且flag为1则表示信息不完整,否则可以进行排序
此题目的分析中判断出先处理A=B的情况,将A和B处理成一个点(本题用集合来合并二者,以后如果用到A或者B,则用树根表示)是关键,原因如下:
如果按照顺序处理,在建图的时候无法处理A=B的情况,如果不添加A和B间的关系,则无法处理A=B,A>C,C>B的情况;如果转换成A>B,B>A则无法处理C>A,C<B,A=B的情况。
因此好的做法就是先将A=B的情况合并成一个点,这样就可以很好处理之后遇到的关系了。
空间复杂度为O(N+M),建立一个集合的时间复杂度为O(1),N次合并M次查找的时间复杂度为O(MAlpha(N)),这里Alpha是Ackerman函数的某个反函数,在很大的范围内(人类目前观测到的宇宙范围估算有10的80次方个原子,这小于前面所说的范围)这个函数的值可以看成是不大于4的,所以并查集的操作可以看做是线性的。
数据结构:并查集
图论:拓扑排序
第一次做混合两种想法的题目,花了大概三天时间始终没有搞定,最后看了结题报告,感觉这确实是一道好题,混合了并查集和图论中的基本知识。同时,发现自己在图论方面比较弱,需要做题加强。
通过接着一道题目,我觉得在看过网上的思路之后,应该按照自己的理解先敲一遍代码,然后对比分析,这样效果可能会好一些。其次,当遇到多种算法混合问题的时候,心里要清楚以哪种算法为重点(即最终解题用的是哪种算法),哪种算法为辅助(即辅助生成能够使用解题算法的辅助数据)。本题目中拓扑排序时重点,并查集是辅助处理A=B的情况,从而让题目可以顺利使用图论的拓扑排序算法。
#include <iostream> #include <vector> #include <queue> using namespace std; typedef struct Node1811 { int p,in; vector<int> rect; } Node1811; Node1811 f1811[10005]; typedef struct Elem1811 { int i,j; char op; } Elem1811; Elem1811 e1811[20005]; queue<int> q1811; int N,M,pos; void makeset1822() { for(int i=0;i<10005;++i) f1811[i].p=i,f1811[i].in=0,f1811[i].rect.clear(); while(!q1811.empty()) q1811.pop(); } int find1811(int x) { return x == f1811[x].p ? x : (f1811[x].p = find1811(f1811[x].p));//路径压缩 } void union1811(int i,int j)//主要用于处理A=B的情况 { int a=find1811(i); int b=find1811(j); if(a==b) return; f1811[b].p=a; //f1811[a].p=b;//由于本函数主要用于处理A=B的情况,所以可以用于替换上边的两句 } int tuopusort1811() { int result=0,data; for(int i=0;i<N;++i) if(find1811(i)==i && f1811[i].in==0) q1811.push(i); while(!q1811.empty()) { //如果大于1,说明这些点之间没有任何关系,故在排序的时候属于UNCERTAIN //并且程序不能立即返回,因为题目要求是“如果信息中同时包含冲突且信息不完全,就输出CONFLICT”,即UNCERTAIN优先级低,因此需要继续运行判断是否出现CONFLICT if(q1811.size()>1) result=2; data=q1811.front();q1811.pop();--pos; for(int i=0;i<f1811[data].rect.size();++i) { if(--f1811[ f1811[data].rect[i] ].in==0) q1811.push(f1811[data].rect[i]);//可以用于替换下边的两句,如果减到0,下次肯定减到-1,判断条件不成立,所以不会重复进队列 //if(f1811[ f1811[data].rect[i] ].in>0) --(f1811[ f1811[data].rect[i] ].in); //if(f1811[ f1811[data].rect[i] ].in==0) q1811.push(f1811[data].rect[i]); } } if(pos > 0) return 1;//存在环,说明CONFLICT return result; } int main() { //freopen("in.txt","r",stdin); int flag; while(cin>>N>>M) { pos=N; flag=0,makeset1822(); for(int i=0;i<M;++i) { cin>>e1811[i].i>>e1811[i].op>>e1811[i].j; if(e1811[i].op == '=') union1811(e1811[i].i,e1811[i].j),--pos; } for(int i=0;i<M;++i) { if(flag==1 || e1811[i].op == '=') continue; int a=find1811(e1811[i].i); int b=find1811(e1811[i].j); if(a == b)//冲突 flag=1; switch(e1811[i].op) { case '>':{ //if(find(f1811[a].rect.begin(),f1811[a].rect.end(),b)==f1811[a].rect.end())//保证同一元素不会重复出现在父节点的vector中 { ++f1811[b].in; f1811[a].rect.push_back(b); } }break; case '<':{ //if(find(f1811[b].rect.begin(),f1811[b].rect.end(),a)==f1811[b].rect.end())//保证同一元素不会重复出现在父节点的vector中 { ++f1811[a].in; f1811[b].rect.push_back(a);//该处会导致同一元素重复出现在父节点的vector中,但是tuopusort()函数保证重复元素不会重复进队列 } }break; } } if(flag!=1) flag=tuopusort1811(); switch(flag) { case 1:cout<<"CONFLICT"<<endl;break; case 2:cout<<"UNCERTAIN"<<endl;break; default:cout<<"OK"<<endl; } } return 0; }
http://blog.csdn.net/xymscau/article/details/7185828 参考了代码
http://gzhu-101majia.iteye.com/blog/1284183 可以借鉴题目的分析思路
http://www.icedy.com/?p=1032 可以借鉴题目的分析思路,他的代码时间似乎短一些
http://www.cnblogs.com/kane0526/archive/2013/01/11/2856890.html 可以借鉴结题思路