【算法学习笔记】46.拓扑排序 优先队列 SJTU OJ 3010 Complicated Buttons

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

*/

 




posted @ 2015-05-08 20:45  雨尘之林  阅读(1277)  评论(0编辑  收藏  举报