【算法学习笔记】46.拓扑排序 优先队列 SJTU OJ 3010 Complicated Buttons
2015-05-08 20:45
375 查看
Description
凯恩在遗迹探险时遇到了n个按钮,刚开始所有按钮都处于开状态,凯恩的经验告诉他把所有按钮都关上会有“好事”发生,可是有些按钮按下时会让其他一些已经闭合的按钮弹开,经过凯恩研究,每个按钮都对应着一个固定的弹开集合,这个按钮按下时,弹开集合中所有的按钮都会变为开状态。现在小k想知道是否能让所有的按钮变为闭状态。如果能,打印最少步数以及方案,否则,打印“no solution”。Input Format
第一行一个整数n,表示n个按钮。接下来n行,表示编号为1到n个按钮的弹开集合。格式为 mi B1 B2 B3 … Bmi表示编号为i的按钮按下,会让编号为B1 B2 B3… Bmi的按钮弹开(注:其中不会出现重复)。1 <= n <= 30000 记 M = m1+m2+…+mn, 0 <= M <= 1000000, 对于70%的数据n <= 300
Output Format
如果无解,输出”no solution”。否则,第一行输出最少步数ans,第二行输出用空格分开的ans个数,表示按顺序按下编号为这些数的按钮就可以解决。如果有多种方案,输出字典序最小的方案。Sample Input
6 2 2 3 0 2 4 5 0 0 0
Sample Output
6 1 2 3 4 5 6经指点,知道这个题主要涉及了两件事情。一个是拓扑排序,一个叫做优先队列(貌似和堆是一回事?)
关于拓扑排序:http://www.cnblogs.com/newpanderking/archive/2012/10/18/2729552.html
http://blog.csdn.net/fisher_jiang/article/details/941234
关于优先队列:http://www.cppblog.com/shyli/archive/2007/04/06/21366.html
(有一个疑问就是:这个题里用优先队列而不用队列是为了生成字典序的结果,还是同时能起到优化的作用?(上次在做最短路径的时候,好像记得Dijkstra算法可以通过 优先队列优化/堆优化..一直不懂是怎么个原理。))
在这个题中,要达到的效果是: 按下一个按钮时,要保证所有能够使这个按钮弹开的按钮都已经被按下。
由于拓扑序列一共有n项,所以输出的第一行一定是n
然后就按照拓扑序列的过程进行即可, 有一点要注意的是, 要时刻判断正在处理的点是否是已经被删除的点, 哪怕有可能没必要的判断也不要省略 第6个测试点一直RE,才意识到队列中点可能已经是被删除过的,这种环路应该还是很多的。另外注意动态数组的使用,还有代码的模块化。[/code]
#include <iostream> #include <queue> #include <cstdlib> #include <cstdio> #include <cstring> using namespace std; //全局变量 const int MaxN = 30000+5; //int g[MaxN][MaxN]={0}; int** g; //邻接表存储图 注意g[i] 表示i号按钮的情况 g[i][0]存储的是它的出边的个数 g[i][1~g[i][0]]存储的是这些边 int in[MaxN]={0};//in[i]存储的是这个点的入度 bool del[MaxN]={0};//del[i]表示是否已经被删除 //优先队列: 指定了比较方法为数值小的优先级高 从而实现字典序 priority_queue< int,vector<int>,greater<int> > q;//如果不指定greater<int>为比较函数的话 系统会自动调用<来进行比较 int n;//按钮个数 int ans[MaxN]={0};//记录存储结果 //初始化输入图 void init(){ scanf("%d",&n); g = new int*[n+5]; for (int i = 1; i <= n; ++i)//对每个节点的边 { int m = 0; scanf("%d",&m);//记录这个点的边的数目 g[i] = new int[m+10]; g[i][0] = m; for (int j = 1; j <= g[i][0]; ++j) { scanf("%d",&g[i][j]); in[g[i][j]]++;//第j个点的入度加一 } } } //返回是否可以进行拓扑排序 bool TopologicalSort(){ //先放入所有入度为0的点 for (int i = 1; i <= n; ++i) if(in[i]==0) q.push(i); if(q.size()==0) //没有入度为0的点... return false; //拓扑排序的结果一定是n位 所以用for指定次数 for (int i = 1; i <= n; ++i) { int cur = q.top();//堆的形象出来了 q.pop(); if(del[cur]) return false; del[cur] = true;//删除这个点 ans[i] = cur; //删除这个点的所有边 for (int j = 1; j <= g[cur][0]; ++j) { int nxt = g[cur][j]; if(del[nxt]) return false;//如果它连接了一个已经被删除的点 说明有环存在 in[nxt]--;//让它连接的那个点的入度减一 if(in[nxt]==0) q.push(nxt); } } return true; } void destory(){ for (int i = 0; i < n+5; ++i) { delete[] g[i]; } } int main(int argc, char const *argv[]) { init(); if(TopologicalSort()){ printf("%d\n", n); for (int i = 1; i <= n; ++i){ printf("%d ",ans[i]); } printf("\n"); }else printf("no solution\n"); destory(); return 0; } /* AOV网:顶点活动网络 把一个有向无环图(DAG)进行拓扑排序,得到的次序就说明了,在进行某一项活动时,它的前驱(必要)活动都已经完成。 拓扑排序:对DAG进行拓扑排序。 得到一个线性序列使得如果DAG中存在u->v,则在u在v的前面。 在这个题中,AOV指的是,按下一个按钮时,要保证所有能够使这个按钮弹开的按钮都已经被按下。 拓扑排序的步骤很简单。。 1.循环找到一个入度为0的点,把它和它的出边都从中图中删除。 2.如果图里最后剩下点,说明存在回路。 PS:堆和优先队列..貌似是一个事情/.. 直观地,可以认为把队列作为横坐标 纵坐标为优先级.形成一个沙堆 每次从上向下拿东西 */
相关文章推荐
- 【算法学习笔记】79.STL 优先队列 模拟法 SJTU OJ 4012 合并果子
- 【算法学习笔记】53.单调队列的简单应用 SJTU OJ 1034 二哥的金链
- 【算法学习笔记】63. BFS SJTU OJ 1281 蹦蹦跳跳
- 【算法学习笔记】70.回文序列 动态规划 SJTU OJ 1066 小M家的牛们
- 【算法学习笔记】57. 前缀树 字典序优化技巧 STL学习 SJTU OJ 1366 前缀匹配
- 【算法学习笔记】74. 枚举 状态压缩 填充方案 SJTU OJ 1391 畅畅的牙签袋(改)
- 【算法学习笔记】49.暴力穷举 BFS 剪枝 SJTU OJ 1357 相邻方案
- 【算法学习笔记】87. 枚举路径 SJTU OJ 1999 二哥找宝藏
- 【算法学习笔记】42.正反DP 填充问题 SJTU OJ 1285 时晴时雨
- 【算法学习笔记】38.最短路问题 SJTU OJ 1105 path
- 【算法学习笔记】47.高精度x低精度 组合数学 杨辉三角 SJTU OJ 3003 Strange Mushroom
- 【算法学习笔记】32.计算几何 求含最多给定点的直线 SJTU OJ 1350 穿越沙漠
- 【算法学习笔记】51. 贪心法 区间排序问题 SJTU OJ 1360 偶像丁姐的烦恼
- 【算法学习笔记】24.记忆化搜索 解题报告 SJTU OJ 1002 二哥种花生
- 【算法学习笔记】85.破环为链 序列DP 松弛+代价 SJTU OJ 1073 能量项链
- 【算法学习笔记】28.枚举法 解题报告 SJTU OJ 1255 1256 魔戒
- 【算法学习笔记】88.显式DFS SJTU OJ 2202. 梅西的过人
- 【算法学习笔记】44. 并查集补充 SJTU OJ 3015 露子的星空
- 【算法学习笔记】78. STL二分的练习 下标映射的处理技巧 SJTU OJ 1053 二哥的内存
- 【算法学习笔记】66. 模拟法 数组链表 报数优化 SJTU OJ 4010 谁最有耐心