CSP201509-4(高速公路)(Java 90分)

探讨如何利用Kosaraju算法计算城市间高速公路网络的便利城市对数量,涉及图论中的强连通分量概念及其实现细节。

问题描述
  某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
  现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
  国王想知道,在大臣们给他的计划中,有多少个便利城市对。
输入格式
  输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
  接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。
输出格式
  输出一行,包含一个整数,表示便利城市对的数量。
样例输入
5 5
1 2
2 3
3 4
4 2
3 5
样例输出
3
样例说明
在这里插入图片描述
思路:这个题题意很明显,就是求强连通分量,对于任意一个顶点数大于等于2的 强连通分量,假设强连通分量内的顶点数为n,那么该强连通分量共有n*(n-1)/2个遍历城市对。所以重点就是求强连通分量。我用了Kosaraju算法。
求强连通分量的有名算法一共有两个 Tarjan 和 Kosaraju。
Kosaraju 容易理解,但算法时间复杂度较高,容易爆内存
Tarjan 难理解,但是算法时间复杂度低,相比于 Kosaraju不容易爆内存
关于这两个算法的详细解释请移步 https://www.cnblogs.com/reddest/p/5932153.html
我的Java代码90分,原因是爆内存了。
这是我的提交记录在这里插入图片描述
第一次提交时得了90分,提示运行错误
然后我找原因,没找到,继续提交,一模一样的代码居然变成80分了。
我又试了一次还是80分。。
然后我把

G[i]=new ArrayList<>();
rG[i]=new ArrayList<>();

改成了

G[i]=new ArrayList<>(n);
rG[i]=new ArrayList<>(n);

提交以后变成0分了。。
所以总结出来就是爆内存了。。

接下来贴我的代码:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
public class D {
	static int V;//顶点数
	static List<Integer> G[];//图的邻接表示
	static List<Integer> rG[];//把边反向后的图
	static List<Integer> vs;//后序遍历的顶点序列
	static boolean used[];//访问标记
	static int cmp[];//所属强连通分量的拓扑序
	static Map<Integer, Integer> map;//每个强联通分量的顶点个数
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m =sc.nextInt();
		V = n;
		G =new ArrayList[n];
		rG = new ArrayList[n];
		for(int i=0;i<n;i++) {
			G[i]=new ArrayList<>();
			rG[i]=new ArrayList<>();
		}
		vs =new ArrayList<>();
		used  = new boolean[n];
		cmp = new int[n];
		map = new HashMap<Integer, Integer>();
		
		for(int i=0;i<m;i++) {
			add_edge(sc.nextInt()-1, sc.nextInt()-1);
		}
		int tmp = scc();
		int result=0;
		for(int i=0;i<tmp;i++) {
			int t1 = map.get(i);
			if(t1>=2) {
				int t2=t1*(t1-1)/2;
				result+=t2;
			}
		}
		System.out.println(result);
		
	}
	static void add_edge(int from,int to) {
		G[from].add(to);
		rG[to].add(from);
	}
	static void dfs(int v) {
		used[v] = true;
		for(int i=0;i<G[v].size();i++) {
			if(!used[G[v].get(i)]) {
				dfs(G[v].get(i));
			}
		}
		vs.add(v);
	}
	static void rdfs(int v,int k) {
		used[v] = true;
		
		cmp[v]=k;
		for(int i=0;i<rG[v].size();i++) {
			if(!used[rG[v].get(i)]) {
				rdfs(rG[v].get(i),k);
			}
		}
	}
	public static int scc() {
		for(int i=0;i<used.length;i++) {
			used[i]=false;
		}
		for(int i=0;i<V;i++) {
			if(!used[i]) {
				dfs(i);
			}
		}
		for(int i=0;i<used.length;i++) {
			used[i]=false;
		}
		int k=0;
		for(int i=vs.size()-1;i>=0;i--) {
			if(!used[vs.get(i)]) {
				rdfs(vs.get(i),k);
				k++;
			}
		}
		for(int i=0;i<V;i++) {
			if(map.containsKey(cmp[i])) {
				map.put(cmp[i], map.get(cmp[i])+1);
			}
			else {
				map.put(cmp[i],1);
			}
		}
		return k;
	}
}

等有时间我会尝试用Tarjan 算法重做一遍。
如有错误,欢迎指正。

### CSP202312-2 因子化简 Java 实现 #### 质数筛选与存储 为了高效解决此问题,首先需要准备一个函数来生成并保存不超过1000的所有质数列表。这一步骤利用埃拉托斯特尼筛法完成,可以显著减少后续运算中的重复工作量[^2]。 ```java import java.util.ArrayList; import java.util.Arrays; public class FactorSimplification { private static final int MAX_PRIME = 1000; // 定义最大质数值范围 public static ArrayList<Integer> generatePrimes(int max) { boolean[] isPrime = new boolean[max + 1]; Arrays.fill(isPrime, true); isPrime[0] = false; isPrime[1] = false; for (int i = 2; i * i <= max; ++i) { if (!isPrime[i]) continue; for (int j = i * i; j <= max; j += i) { isPrime[j] = false; } } ArrayList<Integer> primes = new ArrayList<>(); for (int i = 2; i <= max; ++i) { if (isPrime[i]) primes.add(i); } return primes; } } ``` #### 主逻辑实现 接下来定义主方法`factorizeAndSimplify`用于接收参数n和k,并返回简化后的结果。该过程涉及遍历预先计算好的质数表,尝试除尽给定整数n直到无法再被当前质数整除为止;同时记录下每个成功匹配到的质因数及其对应的幂次方数量。当某个特定质因数的数量达到或超过指定阈值k时,则将其纳入最终乘积计算之中[^3]。 ```java public long factorizeAndSimplify(long n, int k, ArrayList<Integer> primes) { long result = 1L; for (Integer prime : primes) { if ((long)prime * prime > n) break; int count = 0; while (n % prime == 0) { n /= prime; count++; } if (count >= k) { result *= Math.pow(prime, count / k * k); // 只考虑能构成至少一次完全项的部 } } if (n != 1 && k == 1) { // 特殊情况处理:剩余的大于sqrt(n)的唯一质因子 result *= n; } else if (n != 1 && k > 1){ result = 1; // 若存在未参与组合的大质因子且其指数不足k,则整体重置为1 } return result == 1 ? 1 : result; } ``` #### 处理多个查询请求 对于多组测试数据的情况,在main入口处读取标准输入流中的Q个询问案例,并依次调用上面编写的辅助函数来进行求解。每次输出单行答案表示对应询问的结果[^1]。 ```java import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.util.StringTokenizer; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = null; StringBuilder sb = new StringBuilder(); int Q = Integer.parseInt(br.readLine()); ArrayList<Integer> primes = FactorSimplification.generatePrimes(FactorSimplification.MAX_PRIME); while(Q-- > 0) { st = new StringTokenizer(br.readLine(), " "); long N = Long.parseLong(st.nextToken()); int K = Integer.parseInt(st.nextToken()); sb.append(FactorSimplification.factorizeAndSimplify(N, K, primes)).append("\n"); } System.out.print(sb.toString().trim()); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值