您的位置:首页 > 其它

【算法学习笔记】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:堆和优先队列..貌似是一个事情/.. 直观地,可以认为把队列作为横坐标 纵坐标为优先级.形成一个沙堆 每次从上向下拿东西

*/

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐