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,才意识到队列中点可能已经是被删除过的,这种环路应该还是很多的。
另外注意动态数组的使用,还有代码的模块化。
#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:堆和优先队列..貌似是一个事情/.. 直观地,可以认为把队列作为横坐标 纵坐标为优先级.形成一个沙堆 每次从上向下拿东西 */
时间: 2024-11-06 11:37:34