2021 RoboCom 世界机器人开发者大赛-本科组(初赛)java题解

目录

7-1 懂的都懂 (20分)

输入格式:

输出格式:

输入样例:

输出样例:

 解析:

代码:

7-2 芬兰木棋 (25分)

输入格式:

输出格式:

输入样例:

输出样例:

解析:

代码:

7-3 打怪升级 (25分)

输入格式:

输出格式:

输入样例:

输出样例:

解析:

代码:

7-4 疫情防控 (30分)

输入格式:

输出格式:

输入样例:

输出样例:

解析:

代码:


7-1 懂的都懂 (20分)

众所周知,在互联网上有很多话是不好直接说出来的,不过一些模糊的图片仍然能让网友看懂你在说什么。然而对这种言论依然一定要出重拳,所以请你实现一个简单的匹配算法。

现在我们采集了原图的一些特征数据,由 N 个小于 255 的非负整数组成,假设对于给定的若干张由 Mi​ 个同样小于 255 的非负整数组成的新图的特征数据,每个数据都可以由原图中任意四个不同数据的平均值计算而来,则称新图为原图的相似图片。对于给出的数据,请你判断是不是相似图片。

注意,不同数据指的并非是数据的值不同,而是不能取同一个数据多次。对于两个相同值的数据,如果给出两次,则可以取两次。

输入格式:

输入第一行是两个整数 N,K (1 ≤ N ≤ 50, 1 ≤ K ≤ 200),表示采集的原图的特征数据个数和新图的张数。

接下来一行为 N 个小于 255 的非负整数,表示原图的特征数据。

最后的 K 行,每行第一个数是 Mi​ (1 ≤ Mi​ ≤ 200),表示新图的特征数据个数。然后是 Mi​ 个小于 255 的非负整数,表示新图的特征数据。

输出格式:

对于每一张新图,如果为相似图片,则在一行中输出 Yes,否则输出 No。

输入样例:

5 3
4 8 12 20 40
3 11 16 19
3 12 16 19
10 11 11 11 11 11 11 11 11 11 11

输出样例:

Yes
No
Yes

代码长度限制                                                                                                                  16 KB

时间限制                                                                                                                        400 ms

内存限制                                                                                                                         64 MB

 解析:

根据题意就是,给出K张新图,只要新图的每个数据都能等于原图的某四个数据之和的平均数,那么就说新图为相似的,要输出Yes,否则输出No。所以这题重点就是如何在原图数据中找出那四个符合条件的,这好像没有什么巧妙的办法。所以我们直接求原图数据的组合数,枚举出每一种四四组合的可能。我们可以算一下时间复杂度,C_{n}^{4}=\frac{n!}{4!(n-4)!},n最大为254,可以算出最大组合数为169362501,诶,虽然很大,但它就是能过。

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

public class Main {
	//oldG保存原图的数据
	static List<Integer> oldG;
	//sum保存原图数据4个数的所有组合
	static Set<Integer> sum;

	public static void main(String[] args) throws IOException { 
		InputReader in = new InputReader();
		int n = in.nextInt(), k = in.nextInt();
		oldG = new ArrayList<Integer>();
		for (int i = 0; i < n; i++) 
			oldG.add(in.nextInt());
		sum = new HashSet<Integer>();
		//因为4个数组合,所以4层循环求组合数,每层循环的起点是上一次循环起点的后一位,避免四个数重复
		for (int i = 0; i < n; i++) {
			for (int j = i + 1; j < n; j++) {
				for (int b = j + 1; b < n; b++) {
					for (int a = b + 1; a < n; a++) {
						sum.add(oldG.get(i)+oldG.get(j)+oldG.get(b)+oldG.get(a));
					}
				}
			}
		}
		boolean flag;
		for (int i = 0; i < k; i++) {
			int m = in.nextInt();
			flag = true;
			for (int j = 0; j < m; j++) {
				int u = in.nextInt();
				//只要有一个数据不符合,后面的数据就不用判断了
				if (!flag) 
					continue;
				if (!sum.contains(u*4)) 
					flag = false;
			}
			System.out.println(flag ? "Yes" : "No");
		}
	}
}

class InputReader{
	BufferedReader buf;
	StringTokenizer tok;
	public InputReader() {
		buf = new BufferedReader(new InputStreamReader(System.in));
	}
	boolean hasNext() {
		while (tok == null || !tok.hasMoreElements()) {
			try {
				tok = new StringTokenizer(buf.readLine());
			} catch (Exception e) {
				return false;
			}
		}
		return true;
	}
	String next() {
		if (hasNext()) {
			return tok.nextToken();
		}
		return null;
	}
	int nextInt() {
		return Integer.parseInt(next());
	}
	long nextLong(){
		return Long.parseLong(next());
	}
	double nextDouble() {
		return Double.parseDouble(next());
	}
	String nextLine() throws IOException {
		return buf.readLine();
	}
}

7-2 芬兰木棋 (25分)

芬兰木棋(Mölkky,又称芬兰木柱)是源自芬兰的一项运动。哲哲将这个运动改造成了赛博朋克单人版,现在场上一开始有 N 根立起的小木棋(上面分别标有一个非负整数),哲哲投掷一根大木棋去击倒这些小木棋以获得分数。分数规则如下:

  • 如果仅击倒 1 根木棋,则得木棋上的分数。
  • 如果击倒 2 根或以上的木棋,则只得击倒根数的分数。(例如击倒 5 根,则得 5 分。)

哲哲固定站在 (0,0) 点上,四周放着若干个小木棋 (Xi​,Yi​),坐标均为整数。每次哲哲可以朝一个方向扔出大木棋,大木棋会打倒这个方向上离哲哲最近的 k 个小木棋。哲哲游戏水平很高超,所以这个 k 可以自由控制。

请问哲哲最多能拿多少分,在获得最多分数的情况下最少需要扔出多少次大木棋?

规则与真实规则有较大出入,真实游玩时请以国际莫尔基组织的规则为准

输入格式:

输入第一行是一个正整数 N (1 ≤ N ≤ 105),表示场上一开始有 N 个木棋。

接下来 N 行,每行 3 个整数 Xi​,Yi​,Pi​,分别表示木棋放置在 (Xi​,Yi​),木棋上的分数是 Pi​。坐标在 32 位整数范围内,分数为小于等于 1000 的正整数。

保证 (0,0) 点没有木棋,也没有木棋重叠放置。

输出格式:

输出一行两个数,表示最多分数以及获得最多分数最少需要投掷大木棋多少次。

输入样例:

11
1 2 2
2 4 3
3 6 4
-1 2 2
-2 4 3
-3 6 4
-1 -2 1
-2 -4 1
-3 -6 1
-4 -8 2
2 -1 999

输出样例:

 1022 9

代码长度限制                                                                                                                  16 KB

Java (javac)

时间限制                                                                                                                      1800 ms

内存限制                                                                                                                       256 MB

Python (python3)

时间限制                                                                                                                        800 ms

内存限制                                                                                                                         64 MB

Python (python2)

时间限制                                                                                                                        800 ms

内存限制                                                                                                                         64 MB

其他编译器

时间限制                                                                                                                        400 ms

内存限制                                                                                                                         64 MB

解析:

 这道题哲哲始终会站在原点(0,0)上不会移动,然后木棋会遍布各个坐标系。哲哲既然不会移动,那他又要击倒所有木棋,就只能通过往不同方向扔来击倒木棋。还有就是,为了使得扔的次数最少,那么分数为1的木棋并且同方向又是连续两个及以上的,只需要扔一次,就可以获得最大的分数,除此之外,每个木棋都需要仍一次。所以这题的问题就是如何辨别木棋是否在同一方向上,并且分数为1的木棋在每个方向上又有几个连续的。确定每个木棋的方向我们可以用斜率来确认,在这里就是y/x来表示斜率,但是我们不能硬生生去y/x来求斜率,因为我们要考虑x不能为0的情况,以及第二、第四象限(-3,8)(3,-8)这两个点的斜率一样但是方向不同的情况。同一方向上的坐标有这样性质,比如(2,1)和(4,2)、(16,8),(4,2)和(16,8)除以它们x和y的gcd后,都能转换为(2,1),所以我们就用它们的坐标除以gcd后的x和y来表示一个方向。在java显然用map来保存木棋很合适,(x,y)作为key表示方向,一个链表保存此方向上的所有木棋作为value。这里保存木棋也很有讲究。因为后面我们需要求连续的分数1木棋,所以我们必须给每个方向上的木棋进行排序。排序依据就用木棋到原点的距离来表示,在这里我们不用真正硬是要求出准确距离,只需要求出d=x*x+y*y就好了,不用去开根号了。然后我们的木棋类就不用保存x和y两个属性,我们只需要保存它的距离d就好了,这也省了很大空间。这题还有个对java不友好的地方就是坐标是32位整数,用long类型是存不了的,所以初始x和y只能用BigInteger来存。但是它的数据幸好除以gcd后,是可以用long类型来保存的。

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;

public class Main {

	public static void main(String[] args) throws IOException { 
		InputReader in = new InputReader();
		int n = in.nextInt();
		//用map来保存各个方向上的木棋
		Map<Slope, ArrayList<Node>> map = new HashMap<Slope, ArrayList<Node>>();
		long max = 0, ans = 0;
		for (int i = 0; i < n; i++) {
			BigInteger x = new BigInteger(in.next());
			BigInteger y = new BigInteger(in.next());
			BigInteger d = x.pow(2).add(y.pow(2));//求出距离
			int p = in.nextInt();
			max += p;
			if (p > 1) //只要分数不为1肯定需要扔一次
				ans++;
			Node node = new Node(d, p);
			BigInteger aa = x.gcd(y);//求出最大公约数用来化简
			Long xl = x.divide(aa).longValue(), yl = y.divide(aa).longValue();
			Slope slope = new Slope(xl, yl);
			map.put(slope, map.getOrDefault(slope, new ArrayList<Node>()));
			map.get(slope).add(node);
		}
		Set set = map.keySet();
		int flag;
		for (Object object : set) {
			Collections.sort(map.get(object));
			flag = 0;
			Node node;
			//使用迭代器遍历ArrayList是很快的
			for (Iterator iterator = map.get(object).iterator(); iterator.hasNext();) {
				node = (Node) iterator.next();
				//这里主要是判断连续的分数为1的木棋
				if (node.p == 1) {
					flag = 1;//分数为1flag就记为1
				} else {
					flag = flag == 1 ? 2 : 0;//上一个是1这一次不是1,说明连续的分数为1的木棋打断了,记为2
				}
				if (flag == 2) //flag为2就说明一个连续的分数为1的木棋打断了,需要次数+1
					ans++;
			}
			//如果连续到最后一个也是分数为1的木棋,就加+1
			if (flag == 1) 
				ans++;
		}
		System.out.println(max + " " + ans);
	}
	
}
//木棋类
class Node implements Comparable<Node>{
	public BigInteger d;//到原点的距离,用来排序
	public int p;//分数
	public Node(BigInteger d, int p) {
		this.d = d;
		this.p = p;
	}
	public int compareTo(Node o) {
		return this.d.compareTo(o.d);
	}
}
//方向类,用来确定一个方向
class Slope{
	public long x;//化简后的x
	public long y;//化简后的y
	public Slope(long x, long y) {
		this.x = x;
		this.y = y;
	}
	public int hashCode() {
		return Objects.hash(x, y);
	}
	public boolean equals(Object obj) {
		Slope other = (Slope) obj;
		return x == other.x && y == other.y;//只要x和y相等就说明是同一个方向
	}
}

class InputReader{
	BufferedReader buf;
	StringTokenizer tok;
	public InputReader() {
		buf = new BufferedReader(new InputStreamReader(System.in));
	}
	boolean hasNext() {
		while (tok == null || !tok.hasMoreElements()) {
			try {
				tok = new StringTokenizer(buf.readLine());
			} catch (Exception e) {
				return false;
			}
		}
		return true;
	}
	String next() {
		if (hasNext()) {
			return tok.nextToken();
		}
		return null;
	}
	int nextInt() {
		return Integer.parseInt(next());
	}
	long nextLong(){
		return Long.parseLong(next());
	}
	double nextDouble() {
		return Double.parseDouble(next());
	}
	String nextLine() throws IOException {
		return buf.readLine();
	}
}

7-3 打怪升级 (25分)

很多游戏都有打怪升级的环节,玩家需要打败一系列怪兽去赢取成就和徽章。这里我们考虑一种简单的打怪升级游戏,游戏规则是,给定有 N 个堡垒的地图,堡垒之间有道路相连,每条道路上有一只怪兽把守。怪兽本身有能量,手里的武器有价值。打败怪兽需要的能量等于怪兽本身的能量,而怪兽一旦被打败,武器就归玩家所有 —— 当然缴获的武器价值越高,玩家就越开心。

你的任务有两件:

  1. 帮助玩家确定一个最合算的空降位置,即空降到地图中的某个堡垒,使得玩家从这个空降点出发,到攻下最难攻克(即耗费能量最多)的那个堡垒所需要的能量最小;
  2. 从这个空降点出发,帮助玩家找到攻克任意一个其想要攻克的堡垒的最省能量的路径。如果这种路径不唯一,则选择沿途缴获武器总价值最高的解,题目保证这种解是唯一的。

输入格式:

输入第一行给出两个正整数 N (≤1000) 和 M,其中 N 是堡垒总数,M 是怪兽总数。为简单起见,我们将堡垒从 1 到 N 编号。随后 M 行,第 i 行给出了第 i 只怪兽的信息,格式如下:

B1 B2 怪兽能量 武器价值

其中 B1 和 B2 是怪兽把守的道路两端的堡垒编号。题目保证每对堡垒之间只有一只怪兽把守,并且 怪兽能量 和 武器价值 都是不超过 100 的正整数。

再后面是一个正整数 K(≤N)和玩家想要攻克的 K 个目标堡垒的编号。

输出格式:

首先在一行中输出玩家空降的堡垒编号 B0。如果有多种可能,则输出编号最小的那个。

随后依次为玩家想要攻克的每个堡垒 B 推荐最省能量的攻克路径,并列出需要耗费的能量值和沿途缴获武器的总价值。注意如果最省力的路径不唯一,则选择沿途缴获武器总价值最高的解。格式为:

B0->途经堡垒1->...->B

总耗费能量 武器总价值

输入样例:

6 12
1 2 10 5
2 3 16 20
3 1 4 2
2 4 20 22
4 5 2 2
5 3 12 6
4 6 8 5
6 5 10 5
6 1 20 25
1 5 8 5
2 5 2 1
2 6 8 5
4
2 3 6 5 

输出样例:

5
5->2
2 1
5->1->3
12 7
5->4->6
10 7
5
0 0 

代码长度限制                                                                                                                  16 KB

时间限制                                                                                                                      5000 ms

内存限制                                                                                                                         64 MB

解析:

这题这个空降位置又是考阅读理解,若不是看别人代码我真的不知道这个空降点是指每个点到它花费的最多的那个点中,又要选其中最小的。就拿题目中这个图来举例。点1的最多花费是18,点2的最多花费是14,点3的最多花费是22,点4的最多花费是14,点5的最多花费是12,点6的最多花费是22。然后在里面选最小的,所以是5,真的无语子。这题就是考最短路径,然后题目要求一个点到其他的点的最短路径,显然用迪杰斯特拉比较好。但是是否需要用堆优化呢?朴素的迪杰斯特拉的时间复杂度是O(n²),而用堆优化的时间复杂度为O(elog_{2}e),n是顶点数,e是边数,题目没有说e最大为多少,那么我们其实知道e最大为n(n-1)/2,这个时候堆优化是大于朴素的。所以这题我们还是用朴素的版本。然后就是如何求空降点,这个只能每个顶点求一次迪杰斯特拉,然后找出花费最大的,然后在这其中找出最小的那个。这题我一直通过不了,我甚至照着别人的满分c++代码复刻成java,测试点也还是答案错误,所以我觉得即使内存时间有待改善,但是逻辑应该是没错的。所以用java通过的兄弟务必告诉我正解,孩子真的很纠结

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
	static int n, m;
	static final int INF = 101;
	//path用来保存路径,path[i]表示i顶点的上个顶点,cost[i]表示空降点到点i的最少花费,award[i]表示空降点到点i的最大价值
	static int[] path, cost, award;
	static boolean[] selected;//selectde[i]表示到点i的最短路是否找到

	public static void main(String[] args) throws IOException { 
		InputReader in = new InputReader();
		n = in.nextInt();
		m = in.nextInt();
		int[][] monsters = new int[n + 1][n + 1];//邻接矩阵保存怪兽值
		int[][] arms = new int[n + 1][n + 1];//邻接矩阵保存武器值
		int min = 0, max = 0, temp = Integer.MAX_VALUE;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				if(i != j) {
					monsters[i][j] = INF;
					arms[i][j] = INF;
				}
			}
		}
		
		for (int i = 0; i < m; i++) {
			int a = in.nextInt(), b = in.nextInt(), monster = in.nextInt(), arm = in.nextInt();
			monsters[a][b] = monster;
			monsters[b][a] = monster;
			arms[a][b] = arm;
			arms[b][a] = arm;
		}
		for (int i = 1; i <= n; i++) {//找出空降点
			max = dijkstra(monsters, arms, i);
			if (temp > max) {
				min = i;
				temp = max;
			}
		}
		System.out.println(min);
		dijkstra(monsters, arms, min);
		int k = in.nextInt();
		for (int i = 0; i < k; i++) {
			int t = in.nextInt();
			if (t == min) {
				System.out.println(t);
				System.out.println("0 0");
				continue;
			}
			System.out.print(min);
			dfs(t, min);
			System.out.println();
			System.out.println(cost[t] + " " + award[t]);
		}
	}
	//dfs用来输出路径,这里运用了回溯的原理
	public static void dfs(int x, int id) {
		if (x == id) 
			return;
		dfs(path[x], id);
		System.out.print("->" + x);;
	}
	public static int dijkstra(int[][] m, int[][] a, int source) {
		int shortestDistance, i ,j;
		int shortestVertex = source;
		cost = new int[n + 1];
		path = new int[n + 1];
		award = new int[n + 1];
		selected = new boolean[n + 1];
		selected[source] = true;
		for (i = 1; i <= n; i++) {
			cost[i] = m[source][i];
			award[i] = a[source][i];
			path[i] = m[source][i] != INF ? source : -1;
		}
		for (i = 1; i < n; i++) {
			shortestDistance = INF;
			for (j = 1; j <= n; j++) {//找出花费最小的
				if (shortestDistance > cost[j] && !selected[j]) {
					shortestDistance = cost[j];
					shortestVertex = j;
				}
			}
			selected[shortestVertex] = true;
			for (j = 1; j <= n; j++) {//花费最小的点加进来后是否会影响其他路径
				if (!selected[j]) {
					if (cost[shortestVertex] + m[shortestVertex][j] < cost[j]) {
						cost[j] = cost[shortestVertex] + m[shortestVertex][j];
						award[j] = award[shortestVertex] + a[shortestVertex][j];
						path[j] = shortestVertex;
					}else if (cost[shortestVertex] + m[shortestVertex][j] == cost[j] && award[shortestVertex] + a[shortestVertex][j] > award[j]) {
						path[j] = shortestVertex;
						award[j] = award[shortestVertex] + a[shortestVertex][j];
					}
				}
			}
		}
		int max = -1;
		for (i = 1; i <= n; i++) 
			if (max < cost[i]) 
				max = cost[i];
		return max;
	}
	
}


class InputReader{
	BufferedReader buf;
	StringTokenizer tok;
	public InputReader() {
		buf = new BufferedReader(new InputStreamReader(System.in));
	}
	boolean hasNext() {
		while (tok == null || !tok.hasMoreElements()) {
			try {
				tok = new StringTokenizer(buf.readLine());
			} catch (Exception e) {
				return false;
			}
		}
		return true;
	}
	String next() {
		if (hasNext()) {
			return tok.nextToken();
		}
		return null;
	}
	int nextInt() {
		return Integer.parseInt(next());
	}
	long nextLong(){
		return Long.parseLong(next());
	}
	double nextDouble() {
		return Double.parseDouble(next());
	}
	String nextLine() throws IOException {
		return buf.readLine();
	}
}

7-4 疫情防控 (30分)

疫情尚未结束,严防疫情反复。为了做好疫情防控工作,国内设置了地区风险等级,对于中高风险地区的人员采取限制移动、居家隔离等手段。

为了研究疫情防控对于跨地区交通运输的影响,假设现在有 N 个机场,M 条航线,每天都会新增一个防控地区,一个防控地区会导致一个机场无法正常运作,航线也自然无法正常运行,每天会有 Qi​ 对旅客从 Xi​ 机场前往 Yi​ 机场,请计算有多少对旅客会受到影响无法完成行程。

旅客只要能直达或通过若干次中转,且乘坐的所有航线的出发和到达机场都正常运作,即视作可完成行程。

输入格式:

输入第一行是三个整数 N,M,D (1≤N≤5×104, 1≤M≤2×105, 1≤D≤103), 表示机场数、航线数以及新增防控地区的天数。

接下来首先有 M 行,每行给出空格分隔的两个数字 A 和 B,表示编号为 A 和 B 的机场之间有一条航线。航线是双向的,机场编号从 1 到 N。

然后是 D 块输入,每块输入内第一行为空格分隔的两个整数 C 和 Q (1≤Q≤103),表示新增机场编号为 C 所在的城市为防控地区,今天有 Q 段行程。数据保证新增的城市之前一定不是防控地区。

接下来的 Q 行,每行是空格分隔的两个数字 X 和 Y,表示编号为 X 和 Y 的机场的一段行程。行程有可能包括之前就已经成为防控地区的城市。

输出格式:

对于每天的询问,请在一行中输出在新增了一个防控地区后当天的行程有多少不能成行。

输入样例:

5 5 3
1 2
1 3
1 5
2 5
3 4
4 3
1 3
1 4
2 3
5 3
3 4
2 3
3 5
1 3
2 3
2 5
3 4 

输出样例:

1
2

代码长度限制                                                                                                                  16 KB

Java (javac)

时间限制                                                                                                                      2500 ms

内存限制                                                                                                                       256 MB

Python (python3)

时间限制                                                                                                                      3500 ms

内存限制                                                                                                                         64 MB

Python (python2)

时间限制                                                                                                                      3500 ms

内存限制                                                                                                                         64 MB

其他编译器

时间限制                                                                                                                      1000 ms

内存限制                                                                                                                         64 MB

解析:

这题其实就是每次删除一个点C(防控地区就是要删除的点),然后给出Q次询问,问你两个点之间是否有路可达(是否在同一个连通块)。这里虽然有思路实现,但是普通的解法肯定是不能满分通过的。接下来我们就用题目的测试数据来举例分析一下。

 这道题我们主要是找两个点是否是在同一个连通块中,所以我们可以用并查集来保存图,同一个根结点就说明这两个顶点是在互通的(为什么要用并查集,因为并查集查起来快啊)。但是从图中可以看到,我们每输入一次C Q,就会删除一个点,但是并查集里要删除一个点不好删啊。那我们就反转思想,一开始先不要正序处理,我们把所有输入保存起来,倒叙处理。就比如看图上,最后一次C Q输入,此时只有2和3这两个点,我们就只构建包含2和3的并查集,此时又满足题目要求又操作方便。那下一个处理就是C Q=5 3这一次了,此时多包含了一个点1,那我们就把1的有关边也加进去就好了。这里要用LinkedList,用ArrayList就会内存超限。

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

public class Main {
	static int[][] edges;//保存所有边的信息
	static int[] pre;//并查集
	static List<Question>[] questions;//保存所有询问
	static int[] ans;//保存答案
	static int[] cs;//保存要删除的点
	static List<Integer>[] cut;//用邻接表保存要删除的点的有关边
	static boolean[] flag;//表示所有点中哪些是要删除的
	
	public static int find(int x) {
		if (x != pre[x]) 
			pre[x] = find(pre[x]);
		return pre[x];
	}
	
	public static void main(String[] args) { 
		InputReader in = new InputReader();
		int n = in.nextInt(), m = in.nextInt(), d = in.nextInt();
		pre = new int[n + 1];
		edges = new int[m + 1][2];
		questions = new LinkedList[d + 1];
		ans = new int[d + 1];
		cs = new int[d + 1];
		cut = new LinkedList[n + 1];
		flag = new boolean[n + 1];
		//初始化并查集
		for (int i = 1; i <= n; i++) 
			pre[i] = i;
		//保存边集
		for (int i = 1; i <= m; i++) {
			edges[i][0] = in.nextInt();
			edges[i][1] = in.nextInt();
		}
		for (int i = 0; i < cut.length; i++) 
			cut[i] = new LinkedList<Integer>();
		
		for (int i = 1; i <= d; i++) {
			int c = in.nextInt(), q = in.nextInt();
			cs[i] = c;//要删除哪个点保存按顺序保存起来
			flag[c] = true;//索引表示点,标记为要删除的
			questions[i] = new LinkedList<Question>();
			while (q-- > 0) {//把每次询问保存起来
				int x = in.nextInt(), y = in.nextInt();
				questions[i].add(new Question(x, y));
			}
		}
		//这个循环是遍历所有边,看看边的两个顶点是否有要删的点
		//如果有的话就把这个边保存起来,留到后面再添加进去
		for (int i = 1; i <= m; i++) {
			int x = edges[i][0], y = edges[i][1];
			if (flag[x] || flag[y]) {
				if (flag[x]) 
					cut[x].add(y);
				if (flag[y]) 
					cut[y].add(x);
			} else {
				pre[find(x)] = find(y);//构建最初始的并查集,没有任何要删除的点
			}
		}
		//倒叙处理每一次询问
		for (int i = d; i > 0; i--) {
			for (Question q : questions[i]) //看看这一组询问有几个不满足要求
				if (find(q.x) != find(q.y)) 
					ans[i]++;
			for (Integer node : cut[cs[i]]) {
				if (flag[node]) //如果此次要删除的点的边中要包含前面要删除的点,就不能把这条边添加进来
					continue;
				pre[find(node)] = find(cs[i]);//把此次删除的点添加进来
			}
			flag[cs[i]] = false;//添加进来后就不是要删除的点了
		}
		for (int i = 1; i <= d; i++) 
			System.out.println(ans[i]);
	}
	
}
//询问类,保存每次的两个点
class Question{
	public int x;
	public int y;
	public Question(int x, int y) {
		this.x = x;
		this.y = y;
	}
}


class InputReader{
	BufferedReader buf;
	StringTokenizer tok;
	public InputReader() {
		buf = new BufferedReader(new InputStreamReader(System.in));
	}
	boolean hasNext() {
		while (tok == null || !tok.hasMoreElements()) {
			try {
				tok = new StringTokenizer(buf.readLine());
			} catch (IOException e) {
				return false;
			}
		}
		return true;
	}
	String next() {
		if (hasNext()) 
			return tok.nextToken();
		return null;
	}
	int nextInt() {
		return Integer.parseInt(next());
	}
	String nextLine() throws IOException {
		return buf.readLine();
	}
}

NOIP 2018 普及初赛第1028题的题解如下: 题目描述: 给定一个正整数N,要求编写一个程序,计算出它的阶乘N!。阶乘N!是所有小于或等于N的正整数的乘积,且0!定义为1。例如:5! = 5 × 4 × 3 × 2 × 1 = 120。 输入描述: 输入仅包含一个正整数N,其范围为1到20。 输出描述: 输出为计算得到的阶乘N!的值。 解题思路: 1. 使用一个数来存储中间计算结果。 2. 从1开始,依次计算到N的所有整数的阶乘。 3. 每计算出一个数的阶乘,就将其乘到数中,更新数的值。 4. 最终数存储的就是N!的结果。 注意点: - 由于N的范围为1到20,而20!的结果是一个非常大的数,普通的数据类型无法存储,因此需要使用数来模拟大数运算。 - 在实现大数乘法时,需要注意进位的问题。 以下是一个简化的伪代码示例: ``` 输入:N 创建一个足够大的数result用于存储结果 result[0] = 1 // 初始化结果为1 对于i从1到N: carry = 0 // 进位初始化为0 对于j从0到result的长度减1: temp = result[j] * i + carry result[j] = temp % 10 // 更新当前位的值 carry = temp / 10 // 计算新的进位 结束循环 如果carry不为0,则继续添加进位 结束循环 输出result数(从后往前输出,以得到正确的顺序) ``` 实际编程时,需要注意数的索引处理和进位处理,以及在输出时避免在前面输出不必要的零。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值