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

*/
				
时间: 2024-08-24 03:37:04

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

【算法学习笔记】51. 区间排序问题 SJTU OJ 1360 偶像丁姐的烦恼

Description 成为LL冠军的人气偶像丁姐最近比较烦,许多商业活动找上门来.因为每次商业活动给的毛爷爷都一样,所以丁姐希望能够尽可能多的参加这些活动.然而,商业活动的起止时间并不由丁姐说了算,因此丁姐想写一个程序,求出他最多能够参加的商业活动的数量. Input Format 第一行一个数n,表示可选活动的数量. 接下n行每行两个数,表示每个活动开始时间t1_i和结束的时间t2_i. Output Format 一个数字,表示丁姐最多能够参加的活动的数量. Sample Input 10

【算法学习笔记】50.字符串处理 SJTU OJ 1361 丁姐的周末

Description 丁姐来到了神秘的M78星云,为了成为和凹凸曼一样强大的男人有朝一日回到地球拯救世界,丁姐开始了刻苦的学习.但丁姐先要知道在M78星云上一周有多少天,这样他才能知道什么时候是周末可以带妹子出去玩.他找到一个老凹凸曼,但是老凹凸曼自己记性不太好,偶尔会告诉他错误的信息. 凹凸曼会告诉丁姐如下格式的信息: Today is xxxday. Yesterday was yyyend. Tomorrow will be zzzday. 规则1: xxx/yyy/zzz为任意字符串,

【算法学习笔记】43.动态规划 逆向思维 SJTU OJ 1012 增长率问题

1012. 增长率问题 Description 有一个数列,它是由自然数组成的,并且严格单调上升.最小的数不小于S,最大的不超过T.现在知道这个数列有一个性质:后一个数相对于前一个数的增长率总是百分比下的整数(如5相对于4的增长率是25%,25为整数:而9对7就不行了).现在问:这个数列最长可以有多长?满足最长要求的数列有多少个? Input Format 输入仅有一行,包含S和T两个数( 0<S<T≤200000 ). 30%的数据,0<S<T≤100 : 100%的数据,0&l

【算法学习笔记】60.经典动态规划 SJTU OJ 1370 赫萝的桃子

Description 赫萝最喜欢吃蜂蜜腌渍的桃子.然而她能够得到的桃子有限,因此赫萝必须精打细算.赫萝在b天内可以得到a个桃子,每天赫萝至少吃一个桃子,她想知道她在a天内有多少种吃桃子的方法.吃桃子的顺序并不重要,也就是说赫萝认为“第一天吃一个桃子第二天吃两个桃子”和“第一天吃两个桃子第二天吃一个桃子”算一种方法. Input Format 每个测试点有多组测试数据. 第一行一个数n,表示测试的数量. 接下来n行每行两个数a, b(a>b). Output Format 输出n行,每行一个数,

【算法学习笔记】87. 枚举路径 SJTU OJ 1999 二哥找宝藏

这个题只用BFS来搜索一次会很麻烦, 因为每次经过一个宝藏之后,要把所有的vis重置(因为可以重复经过同一点, 但是这样会有很多不必要的路径) 看题目的暗示 最多只有5个宝藏  我们要把所有的宝藏收集齐全, 如果确定了收集的顺序, 那么也就确定了路径 那么可以知道 A55的排列一共是120种路径 遍历起来毫无压力 我们枚举所有宝藏的全排列, 然后从起点开始走, 记录整个路径的步数, 最后取最小值即可. 这里生产全排列的方法利用了 STL的next_permutation函数 非常爽....(要引

【算法学习笔记】70.回文序列 动态规划 SJTU OJ 1066 小M家的牛们

这个题很多地方暗示了DP的路径. 我们处理时,dp[i][j]可以认为是从i坐标到j坐标的序列达到回文效果需要的最小代价,以此向外扩展,最终得到dp[0][M-1]就是结果. 我们要注意到处理dp[i][j]时,我们需要知道 dp(i+1,j-1)的结果,所以i必须降序,j必须升序,才能保证在计算dp(i,j)时,可以利用已经计算过的结果. 所以 i应该从M-2 到 0 递减 j在内层 从i+1到M-1 递增 在处理dp(i,j)时,第一要看name[i]和name[j]是否相等,如果相等的话,

【算法学习笔记】34.高精度除法 SJTU OJ 1026/1016

高精度除法, 这个和加减乘一样,我们都要从手算的角度入手.举一个例子,比如 524134 除以 123.结果是4261 第一位4的来源是 我们把 524和123对齐,然后进行循环减法,循环了4次,余32,将32134的前三位321继续和123对齐,循环减法2次,余75,把7534的前三位753和123对齐,循环减法6次,余15,将154和123对齐,只能减1次,所以结果是4 2 6 1. 把上述过程程序化 1.把A,B两个数存入char数组 0下标表示的是最高位2.把A的前lenB位和B对齐进行

【算法学习笔记】64. 枚举法 SJTU OJ 1381 畅畅的牙签

枚举法就好了,推理很麻烦,感觉也做不出来. 创造一个结构体,一个是真实的数,一个是花费的牙签数. 构建一位数,两位数,三位数即可. #include <iostream> #include <vector> using namespace std; //从0到9耗费的牙签数 int cost[10]={6,2,5,5,4,5,6,3,7,6}; struct num { int n;//用于计算的数 int c;//耗费的牙签 }; num v[100000]; int main(

算法学习 - 图的拓扑排序

拓扑排序 拓扑排序是对有向无圈图的顶点的一种排序,使得如果存在一条从Vi到Vj的路径,那么排序中Vj一定出现在Vi后面. 所以假如图里面有圈就不可能完成排序的. 第一种方法 一种简单的办法就是在排序算法中,先找到任意一个没有入边的顶点,然后显示该顶点,并把它和它的边一起从图里删掉.依次类推到最后. 入度(indegree): 顶点v的入度为,所有指向顶点v的变数(u, v). 出度(outdegree): 顶点v的出度为,顶点v所发出的边数(v, u). 下面写下这种方法的伪代码,因为这个的时间