zoukankan      html  css  js  c++  java
  • 【算法学习笔记】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);
            for (int i = 1; i <= n; ++i){
                printf("%d ",ans[i]);
            }
            printf("
    ");
        }else
            printf("no solution
    ");
        destory();
        return 0;
    }
    
    /*
    AOV网:顶点活动网络 
        把一个有向无环图(DAG)进行拓扑排序,得到的次序就说明了,在进行某一项活动时,它的前驱(必要)活动都已经完成。
    拓扑排序:对DAG进行拓扑排序。
        得到一个线性序列使得如果DAG中存在u->v,则在u在v的前面。
    
    在这个题中,AOV指的是,按下一个按钮时,要保证所有能够使这个按钮弹开的按钮都已经被按下。
    
    拓扑排序的步骤很简单。。
    1.循环找到一个入度为0的点,把它和它的出边都从中图中删除。
    
    2.如果图里最后剩下点,说明存在回路。
    
    
    PS:堆和优先队列..貌似是一个事情/.. 直观地,可以认为把队列作为横坐标 纵坐标为优先级.形成一个沙堆 每次从上向下拿东西
    
    */



  • 相关阅读:
    【WPF】Blend for Visual Studio 2013 入门
    【c#】获取CPU序列号及遇到的问题
    【GIT】学习笔记
    【备份】一些留待学习的好网站
    【VS】无法折叠所有方法的问题 VS2013
    【概念】XML
    配置JDK
    TCP/IP Wireshark监听 及错误代码
    软件比较
    湖南省专升本
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_3010.html
Copyright © 2011-2022 走看看