CCF CSP 高速公路 JAVA 201509-4 100分

来源:互联网 发布:网络销售具体做什么 编辑:程序博客网 时间:2024/05/16 19:11


题目放下面了

两种解法:

1.tarjan算法  专门用来求这种强连通分量,可以解到N<10000,M<100000的数据(1S内)

2.禁忌的解法=。= (感觉和Kosaraju有点像?) 但只能解到N<1000,M<10000的数据(效率差了10倍不止),但优点在于理解简单,容易操作


先说第一种解法:

tarjan算法介绍:https://wenku.baidu.com/view/112c64096c85ec3a87c2c527.html?re=view

——如果看不懂可以看进一步解释:http://www.cnblogs.com/dqsBK/p/5350257.html

——再看不懂就来看看实际运作过程:http://blog.csdn.net/mystery_guest/article/details/51912713

相信到这里一定已经懂了

引用:

dfn数组: 意思就是在dfs的过程中,当前的这个节点是第几个被访问的

low数组: 有些并查集的意思,就是在dfs的过程中,标记如果当前节点是极大强联通子图的话,他的根节点的标号就是对应的low值:

      如果下一个要遍历的节点在栈中,那么就要把当前节点的low值设置成 下一个节点(在栈中)的dfn值,dfn值是什么呢?就是记录这个节点是第几个被遍历到的。

      如果下一个要遍历的节点不再栈中,那么就要把当前节点的low值设置成下一个节点和当前节点的low值中最小的那个。


看懂了过程,只要自己心里多模拟几遍,差不多就掌握这个算法啦:

实际代码:(JAVA版)——JAVA比C++慢一点,C++100分,JAVA同样是90分,不公平TOT

package csp2015_09_4;import java.util.ArrayList;import java.util.List;import java.util.Scanner;import java.util.Stack;public class Main{static List<Integer> list[];static boolean visited[];static int DFN[];static int LOW[];static Stack<Integer> s ;static int index;static int ans = 0;public static void main(String [] args){Scanner sc = new Scanner(System.in);int n = sc.nextInt();int m = sc.nextInt();list = new ArrayList[n+1];for (int i = 0; i <= n; i++) {list[i] = new ArrayList<Integer>();}int a,b;for (int i = 0; i < m; i++) {a = sc.nextInt();b = sc.nextInt();list[a].add(b);}s = new Stack<Integer>();visited = new boolean[n+1];DFN = new int[n+1];LOW = new int[n+1];index = 0;for (int i = 1; i <= n; i++) {if(!visited[i])tarjan(i);}//tarjan(1);System.out.println(ans);}private static void tarjan(int u) {visited[u] = true;DFN[u] = LOW[u] = ++index;s.push(u);int v;for (int i = 0; i < list[u].size(); i++) {v = list[u].get(i);if(!visited[v]){tarjan(v);LOW[u] = Math.min(LOW[u], LOW[v]);}else if(s.contains(v)){LOW[u] = Math.min(LOW[u],DFN[v]);}}if(DFN[u] == LOW[u]){int count = 0;do{v = s.pop();count++;}while(u!=v);if(count>0){ans += count*(count-1)/2;}//自己好好想想为什么!找了半天BUG=。=//if(count>1){//ans += count;//}}}}



然后介绍第二种算法:

简单的说就是深搜一遍,然后得到一个N*N的数组,(vis[i][j]意思是 点i可以连通到点j)(根据这样的定义,我们只要确定点i可以连通到点j时,点j也可以连通到点i,即是vis[i][j] == vis[j][i] == true 那么就说明这两个点强连通,满足题意,ans++)

因为只深搜了一遍,所以只有简单的点i和相邻的各个点j的关系,我们希望当点j和点k相连时,有vis[i][k]连通。

怎么实现呢,最简单的方法就是对每个点都再进行一次深搜,确定起始点s,每深搜到一个点k都意味着s和k直接或间接相连,令vis[s][k] == true即可。

// 由于这样其实是进行了多次的DFS搜索,所以效率肯定是比不上方法一的O(V+E),但也能跑出N<1000,M<10000的数据,当数据量不大的时候这样用用也无妨,至少也算一种思路吧。

代码如下:

1.递归法 2.队列法

注意递归法在数据量很大的时候会爆栈,抛出异常,然后OJ系统就会提示你:运行错误,60分

队列的话就是:超时,60分

至于哪个更快点,不知道,应该也差不了太多,也没有必要深究了。


package csp2015_09_4;/** *  * 用队列 虽然不会爆栈了  但依然超时了=。=  而且空间上使用会大一点  至于哪个更快? 反正两个都超时了 * 重要:经测试:当N<=1000,M<=10000时,可以通过全部数据 * 大于这个数据就别用这个方法了,会超时 *  */import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue;import java.util.Scanner;public class Main_Final{static List<Integer> list[];static boolean vis[][];static boolean ns[];public static void main(String [] args){Scanner sc = new Scanner(System.in);int n = sc.nextInt();int m = sc.nextInt();list = new ArrayList[n+1];for (int i = 0; i <= n; i++) {list[i] = new ArrayList<Integer>();}vis = new boolean[n+1][n+1];int a,b;for (int i = 0; i < m; i++) {a = sc.nextInt();b = sc.nextInt();list[a].add(b);}//递归法://dfs(1);//for (int i = 1; i <= n; i++) {//ns = new boolean[n+1];//ns[i] = true;//dfs2(i,i);//}//队列法:        Queue<Integer> q = new LinkedList();          for (int t = 1; t <= n; t++) {        q.add(t);        ns = new boolean[n+1];        while(!q.isEmpty()){        int temp = q.poll();        if(!ns[temp]){        for (int i = 0; i < list[temp].size(); i++) {        int next = list[temp].get(i);        q.add(next);}        ns[temp] = true;        vis[t][temp] = true;        }        }}int ans = 0;for (int i = 1; i < vis.length; i++) {for (int j = 1; j < vis[i].length; j++) {//System.out.print(vis[i][j]+" ");if(i!=j &&vis[i][j]&&vis[j][i]) {//System.out.println(i+" "+j);ans++;}}//System.out.println();}System.out.println(ans/2);}private static void dfs2(int s, int i) {for (int j = 0; j < list[i].size(); j++) {int to = list[i].get(j);if(!ns[to]){ns[to] = true;vis[s][to] = true;dfs2(s,to);}}}private static void dfs(int i) {for (int j = 0; j < list[i].size(); j++) {if(!vis[i][list[i].get(j)]){int to = list[i].get(j);vis[i][to] = true;dfs(to);}}}}


题目如下:


问题描述
试题编号:201509-4试题名称:高速公路时间限制:1.0s内存限制:256.0MB问题描述:
问题描述
  某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
  现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
  国王想知道,在大臣们给他的计划中,有多少个便利城市对。
输入格式
  输入的第一行包含两个整数nm,分别表示城市和单向高速公路的数量。
  接下来m行,每行两个整数ab,表示城市a有一条单向的高速公路连向城市b
输出格式
  输出一行,包含一个整数,表示便利城市对的数量。
样例输入
5 5
1 2
2 3
3 4
4 2
3 5
样例输出
3
样例说明

  城市间的连接如图所示。有3个便利城市对,它们分别是(2, 3), (2, 4), (3, 4),请注意(2, 3)和(3, 2)看成同一个便利城市对。
评测用例规模与约定
  前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
  前60%的评测用例满足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000;
  所有评测用例满足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。

原创粉丝点击