2014_12_27_topo_related_problems

1000. Ordering Tasks
Total: 1510 Accepted: 333
   
Time Limit: 1sec    Memory Limit:32MB
Description
 John has n tasks to do. Unfortunately, the tasks are not independent and the execution of one task is only possible if other tasks have already been executed.


Input
There are multiple test cases. The first line contains an integer T, indicating the number of test cases. Each test case begins with a line containing two integers, 1 <= n <= 100000 and 1 <= m <= 100000. n is the number of tasks (numbered from 1 to n) and m is the number of direct precedence relations between tasks. After this, there will be m lines with two integers i and j, representing the fact that task i must be executed before task j. It is guaranteed that no task needs to be executed before itself either directly or indirectly. 


Output
For each test case, print a line with n integers representing the tasks in a possible order of execution. To separate them, print exactly one space after each integer. If there are multiple solutions, output the smallest one by lexical order.  


Sample Input
 Copy sample input to clipboard
1
5 5
3 4
4 1
3 2
2 4
5 3 
Sample Output

5 3 2 4 1

*****************************************************************************************

拓扑排序的经典例子,对有向无环图根据edge情况进行排序,有两种思路

广度优先:1.找到入度为0的结点 2.把它的出边全部删掉,出列 3.循环/递归 继续找入度为0的点

深度优先:1.设置visit数组初始化为0 2.任意找visit=0者访问它后面的结点 3.对这个“后面的”结点再判断是否visit、是否有出路,递归直到无路可走,递归期间每一步都设置visit=1 3.头结点出列,其data压入栈顶

伪代码:

            L ← Empty list that will contain the sorted nodes
            S ← Set of all nodes with no outgoing edges
            for each node n in S do
                  visit(n) 
            function visit(node n)
                 if n has not been visited yet then
                       mark n as visited
                 for each node m with an edgefrom m to ndo
                       visit(m)
                 add n to L

refer to    http://blog.csdn.net/dm_vincent/article/details/7714519

refer_begin【为什么在visit方法的最后将该顶点添加到一个集合中,就能保证这个集合就是拓扑排序的结果呢?
因为添加顶点到集合中的时机是在dfs方法即将退出之时,而dfs方法本身是个递归方法,只要当前顶点还存在边指向其它任何顶点,它就会递归调用dfs方法,而不会退出。因此,退出dfs方法,意味着当前顶点没有指向其它顶点的边了,即当前顶点是一条路径上的最后一个顶点。

下面简单证明一下它的正确性:
考虑任意的边v->w,当调用dfs(v)的时候,有如下三种情况:
1.  dfs(w)还没有被调用,即w还没有被mark,此时会调用dfs(w),然后当dfs(w)返回之后,dfs(v)才会返回
2.  dfs(w)已经被调用并返回了,即w已经被mark
3*. dfs(w)已经被调用但是在此时调用dfs(v)的时候还未返回
需要注意的是,以上第三种情况在拓扑排序的场景下是不可能发生的,因为如果情况3是合法的话,就表示存在一条由w到v的路径。而现在我们的前提条件是由v到w有一条边,这就导致我们的图中存在环路,从而该图就不是一个有向无环图(DAG),而我们已经知道,非有向无环图是不能被拓扑排序的。
 
那么考虑前两种情况,无论是情况1还是情况2,w都会先于v被添加到结果列表中。所以边v->w总是由结果集中后出现的顶点指向先出现的顶点。为了让结果更自然一些,可以使用栈来作为存储最终结果的数据结构,从而能够保证边v->w总是由结果集中先出现的顶点指向后出现的顶点。】refer_end


首先给出AC代码,使用vector建图+set存头结点

//topological sorting拓扑排序的使用,有BFS和DFS两种方案
//BF:从小的入度为零的结点开始输出,把它的所有出边删掉并出列,然后从头寻找入度为零的结点
//尝试用数组,但是空间消耗过大(100000^2)
//用链表+new,设置in_degree和visit数组辅助判断是否出列。从头开始check哪个结点的in_degree
//是零,对其的邻接点的in_degree统统减一,然后再找 in_degree=0的。超时
//回忆所学,用静态数组代替动态堆空间,依然超时(不过这种代替的方法需要掌握)
//咨询昱大神,提示我用set这种能自动排序的数据结构存储每一次获取的入度为零的点,超时
//继续改进,链表的插入每次都要循环到尽头,耗时O(n);vector的访问与之原理相似但用时更短 WHY?
//refer to -----http://linxiaoty.iteye.com/blog/1876253 
//DF:算法入门P110提供了深度优先的算法 
#include <cstdio>
#include <cstring>
#include <set>
#include <vector>
using namespace std;

struct node{
	int data;
	node *adjvex;
};
vector<int> g[100005]; <span style="white-space:pre">	</span>//二维vector
set<int> s;
node *l[100005];<span style="white-space:pre">	</span>//模拟动态堆空间
node vex[100005];
int in_degree[100005];
int n, m, i, j, vex_index;

void root_out(){
	while (s.empty() == false){
		set<int>::iterator it = s.begin();
		int num = *it;
		printf("%d ", num);
		s.erase(it);<span style="white-space:pre">		</span>//头结点出列
		vector<int>::iterator v_it = g[num].begin();
		for (; v_it != g[num].end(); v_it++){
			in_degree[*v_it] -= 1;
			if (in_degree[*v_it] == 0)
				s.insert(*v_it);
		}
	}
}

int main(){
	int t;
	while (scanf("%d", &t) != EOF){
		while(t--){
			vex_index = 1;
			memset(l, 0, sizeof(l));
			memset(in_degree, 0, sizeof(in_degree));
			s.clear();
			for (int k = 0; k < 100005; k++)
				g[k].clear();
			//memset(visit, 0, sizeof(visit));
			
			scanf("%d%d", &n, &m);
			for (int k = 1; k <= m; k++){
				scanf("%d%d", &i, &j);
				in_degree[j] += 1;
				g[i].insert(g[i].end(), j);
			}
			for (int k = 1; k <= n; k++)
				if (in_degree[k] == 0)
					s.insert(k);<span style="white-space:pre">	</span>//初始化s,存放入度为0结点data
			root_out();
			printf("\n");	
		}	
	}
	return 0;
}

改进后基于DFS版,可以实现拓扑排序但是暂时没法实现字典序输出 

//lexical ordering TODO

#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
using namespace std;

struct node{
	int data;
	node *adjvex;
};
vector<int> g[100005]; 
stack<int> ans;
int visited[100005];
int n, m, i, j, vex_index;

void visit(int x){
	if (visited[x] == 0){
		visited[x] = 1;
		vector<int>::iterator v_it = g[x].begin();
		for (; v_it != g[x].end(); v_it++){
			visit(*v_it);
		}		
		ans.push(x);<span style="white-space:pre">		</span>//直到没有后续结点,才放入栈
	}
}

int main(){
	int t;
	while (scanf("%d", &t) != EOF){
		while(t--){
			for (int k = 0; k < 100005; k++)
				g[k].clear();
			memset(visited, 0, sizeof(visited));
			
			scanf("%d%d", &n, &m);
			for (int k = 1; k <= m; k++){
				scanf("%d%d", &i, &j);
				g[i].insert(g[i].end(), j);
			}
			for (int k = 1; k <= n; k++)
				visit(k);<span style="white-space:pre">	</span>//逐个访问结点
			for (int k = 1; k <= n; k++){
				printf("%d ", ans.top());
				ans.pop();
			}
			printf("\n");
		}	
	}
	return 0;
}



****************************************************************************************************

1001. DAG?
Total: 662 Accepted: 315
   
Time Limit: 1sec    Memory Limit:256MB
Description
输入一个有向图,判断该图是否是有向无环图(Directed Acyclic Graph)。


Input
输入的第一行包含两个整数n和m,n是图的顶点数,m是边数。1<=n<=100,0<=m<=10000。


接下来的m行,每行是一个数对u v,表示存在有向边(u,v)。顶点编号从1开始。
Output
如果图是DAG,输出1,否则输出0


Sample Input
 Copy sample input to clipboard
3 3
1 2
2 3
3 1
Sample Output
0
Problem Source: 刘晓铭


思路:via baidu zhidao
1.拓扑排序: 还有顶点未输出,但已经不存在没有前驱的顶点了
2.深搜:从一个顶点出发存在搜回到自己的路径

//判断有向图有环 <=>  删除了入度为0结点的所有出边后发现没有其他入度为0的结点了 
#include <cstdio>
using namespace std;

struct node{
	int data;
	node *adjvex;
};

int n, m, u, v;
node *l[102] = {NULL};
int in_degree[102];
int visit[102];
int root_out(){
	int k;
	for (k = 1; k <= n; k++){
		if (in_degree[k] == 0 && visit[k] == 0){
			visit[k] = 1;		//入度=0且未曾访问 
			if (l[k] != NULL){				//删除此节点的所有出边 
				in_degree[l[k]->data] -= 1;	//后续所有结点的入度减一 
				node *pre = l[k], *pro = l[k]->adjvex;
				while(pro != NULL){			//指针移动 
					in_degree[pro->data] -= 1;
					pre = pro;
					pro = pro->adjvex;
				}
			}
			return 1;
		}
	}
	if (k >= n)		//所有结点遍历完成 
		return 0;
}
int main(){
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++){
		scanf("%d%d", &u, &v);		//u->v有边 
		in_degree[v] += 1;			//v入度加一 
		node *temp = new node;
		temp->data = v;
		temp->adjvex = NULL;
		if (l[u] == NULL)
			l[u] = temp;
		
		else{
			node *pre = l[u];
			node *pro = l[u]->adjvex;
			while (pro != NULL){
				pre = pro;			//找到末尾插入 
				pro = pro->adjvex;
			}
			pre->adjvex = temp;					
		}
	}
	bool printed = false;	
	for (int i = 1; i <= n; i++){	
		if (root_out() == 0){		//提前遍历完成说明已经没有入度为0的结点了 
			printf("0\n");
			printed = true;
			break;
		}		
	}
	if (printed == false)
		printf("1\n");
	
		
	return 0;
}

深度搜索回到本身,visit就需要3个状态了:

//DFS搜索回到本身,也说明有环 
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

struct node{
    int data;
    node *adjvex;
};
vector<int> g[100005]; 
queue<int> root;
int visited[100005];
int n, m, i, j, vex_index;
bool flag = true;

void visit(int x){
    if (visited[x] == 0){
        visited[x] = -1;
        vector<int>::iterator v_it = g[x].begin();
        for (; v_it != g[x].end(); v_it++){
            if (visited[*v_it] == -1){
                flag = false;
            }   
            visit(*v_it);
            
        } 
        visited[x] = 1;     
    }
}

int main(){
            for (int k = 0; k < 100005; k++)
                g[k].clear();
            memset(visited, 0, sizeof(visited));
            
            scanf("%d%d", &n, &m);
            for (int k = 1; k <= m; k++){
                scanf("%d%d", &i, &j);
                g[i].insert(g[i].end(), j);
            }
            for (int k = 1; k <= n; k++)
                visit(k);
            if (flag == true)
                printf("1\n");
            else 
                printf("0\n");
    return 0;
}



*************************************************************************************

1002. 无路可逃?
Total: 633 Accepted: 292
   
Time Limit: 1sec    Memory Limit:256MB
Description
唐僧被妖怪关在迷宫中。孙悟空好不容易找到一张迷宫地图,并通过一个魔法门来到来到迷宫某个位置。假设迷宫是一个n*m的矩阵,它有两种地形,1表示平地,0表示沼泽,孙悟空只能停留在平地上。孙悟空目前的位置在坐标(sx,sy)处,他可以向上下左右四个方向移动。


请你帮助孙悟空计算一下,迷宫中是否存在一条路从他所在位置(sx,sy)到达唐僧所在位置(tx,ty)?
 
Input
输入第一行为一个整数t(0<t<=10),表示测试用例个数。


每个测试样例的第1行是2个正整数n (1≤n≤100),m (1≤n≤100),表示迷宫是n*m的矩阵。接下来的n行输入迷宫,每行有m个数字(0或者1),数字之间用一个空格间隔。
接下来的1行是4个整数sx,sy,tx,ty,1≤sx,tx≤n,1≤sy,ty≤m,表示孙悟空所在位置为第sx行第sy列,唐僧所在位置为第tx行第tx列。迷宫左上角编号是(1,1),右下角编号是(n, m)。
数据保证(sx,sy)格和(tx,ty)必定为平地。
 
Output
每个样例单独输出一行:1表示路径存在,0表示路径不存在。


 
Sample Input
 Copy sample input to clipboard
2
2 2
1 0
0 1
1 1 2 2
4 4 
1 1 1 0
1 0 1 1
1 0 1 1
1 1 1 0
1 1 3 4
Sample Output
0
1
Problem Source: 刘晓铭

思路:参考了网上代码,用矩阵做方便很多。每次尝试移动上下左右,直到走到死路。注意到若某几个方向的移动会在某处相交,而该交点往后的状况是可以确定的,即你这一次从这条路径走到这和下一次从另外一条路径走到这的结果是一样的,所以你第一次走过了就可以直接置为0不允许下次再走这条死路。

#include <cstdio>
using namespace std;

int n, m, u, v;
int sun_x, sun_y, tang_x, tang_y;
int road[102][102];
int xmove[4] = {0, 0, -1, 1};
int ymove[4] = {1, -1, 0, 0};				//分别对应上下左右的移动 

int dfs(int x, int y){
	if (x == tang_x && y == tang_y)
		return 1;
	for (int i = 0; i < 4; i++){
		int x_next = x + xmove[i];
		int y_next = y + ymove[i];
		if (x_next > 0 && x_next <= n && y_next > 0 && y_next <= m){
			if (road[x_next][y_next] == 1){
				road[x_next][y_next] = 0;	//不留下回头路,直接置零 
				if (dfs(x_next, y_next))
					return 1;
			}
		}
	}
	return 0;
}

int main(){
	int t;
	scanf("%d", &t);
	while (t--){
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				scanf("%d", &road[i][j]);
		scanf("%d%d%d%d", &sun_x, &sun_y, &tang_x, &tang_y);
		printf("%d\n", dfs(sun_x, sun_y));
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值