JAVA CCF-201403-4 无线网络

欢迎访问我的CCF认证解题目录

 

题目描述

 

思路过程

这道题问最少经过的中转路由器的个数,那么很明显,这是一个最短路径的问题,不过多了个k值得限制,所以我们需要定义一个类来存放坐标、当前剩余的k值,经过中转路由器的个数、标记(是否需要k)。

后面的就跟正常的BFS题差不多了,具体看代码。

但自己在测试数据时,想到了这组测试数据(开始走向误区。。。):

3 3 2 3
0 0
6 6
0 3
3 0
3 3
6 3

经过第一轮,队列有两个点(0,3)和(3,0),其中(3,0)是没有k值的了,如果此时我先拿(3,0)去访问(3,3)并将(3,3)标记为访问,那么此时在(3,3)这个点是没有k值的了,而到达目标还需要一个k。

很显然是需要拿(0,3)去访问(3,3)的,那么我要更改一下BFS,每次遍历完队列的所有点再进行标记,即队列会有两个(3,3)的点,一个有k,一个没有k

 

代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;

class Main {
	
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] line = br.readLine().split(" ");
		int n = Integer.parseInt(line[0]), m = Integer.parseInt(line[1]), k = Integer.parseInt(line[2]);
		long r = Long.parseLong(line[3]);
		Node st = null, ed = null;
		ArrayList<Node> dic = new ArrayList<Node>();//所有点
		ArrayList<Node> start = new ArrayList<Node>();//开始端
		boolean[] flag = new boolean[n+m+1];
		
		//不需要使用k
		for ( int i = 0; i < n; i++ ) {
			line = br.readLine().split(" ");
			long x = Long.parseLong(line[0]), y = Long.parseLong(line[1]);
			if ( i == 0 ) st = new Node(x, y);//起点
			if ( i == 1 ) ed = new Node(x, y);//终点
			dic.add(new Node(x, y));//收入除起点外的所有点
		}
		
		//需要使用k
		for ( int i = 0; i < m; i++ ) {
			line = br.readLine().split(" ");
			long x = Long.parseLong(line[0]), y = Long.parseLong(line[1]);
			dic.add(new Node(x, y, true));
		}
		
		start.add(new Node(st.x, st.y, k, 0));//初始化
		flag[0] = true;
		
		OUT:
		while ( !start.isEmpty() ) {
			
			ArrayList<Node> next = new ArrayList<Node>();
			HashSet<Integer> deleteNum = new HashSet<Integer>();
			
			for ( Node node1 : start ) {
				for ( int i = 0 ; i < dic.size(); i++ ) {
					Node node2 = dic.get(i);
					if ( !flag[i] && isUnicon(node1, node2, r) ) {//如果能联通
						
						if ( node2.flag && node1.k > 0 ) {//如果需要k
							next.add(new Node(node2.x, node2.y, node1.k-1, node1.cnt+1));
						} else if ( !node2.flag ) {//如果不需要k
							next.add(new Node(node2.x, node2.y, node1.k, node1.cnt+1));
						} else continue;
						
						if ( node2.x == ed.x && node2.y == ed.y ) {//如果是终点
							System.out.println(node1.cnt);
							break OUT;
						}
						
						deleteNum.add(i);
					}
				}
			}
			
			for (Integer integer : deleteNum) {
				flag[integer] = true;
			}
			
			start = next;
		}
	}
	
	//判断是否能连接
	public static boolean isUnicon( Node node1, Node node2 , long r ) {
		return (node1.x-node2.x)*(node1.x-node2.x) + (node1.y-node2.y)*(node1.y-node2.y) <= r*r;
	}
}
class Node {
	long x,y,k,cnt;//x,y,k,次数
	boolean flag;//是否需要k
	public Node(long x, long y) {
		this.x = x;
		this.y = y;
	}
	public Node(long x, long y, boolean flag) {
		this.x = x;
		this.y = y;
		this.flag = flag;
	}
	public Node(long x, long y, long k, long cnt) {
		this.x = x;
		this.y = y;
		this.k = k;
		this.cnt = cnt;
	}
	@Override
	public String toString() {
		return "x=" + x + ", y=" + y + ", k=" + k + ", cnt=" + cnt + ", flag=" + flag;
	}
	
}

 

这份代码会重复访问点,所以,超时了。。拿了90分。

这个思路是没问题的,但仔细看数据,是有顺序的,它每次都会先拿不需要k的点去访问,所以上面所说的情况是不会发生的    ( ̄_ ̄|||),直接访问后马上标记就好了。

 

满分代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;

class Main {
	
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] line = br.readLine().split(" ");
		int n = Integer.parseInt(line[0]), m = Integer.parseInt(line[1]), k = Integer.parseInt(line[2]);
		long r = Long.parseLong(line[3]);
		ArrayList<Node> dic = new ArrayList<Node>();//所有点
		LinkedList<Node> q = new LinkedList<Node>();//开始端
		boolean[] flag = new boolean[n+m+1];
		
		//不需要使用k
		for ( int i = 0; i < n; i++ ) {
			line = br.readLine().split(" ");
			long x = Long.parseLong(line[0]), y = Long.parseLong(line[1]);
			dic.add(new Node(x, y));//收入除起点外的所有点
		}
		
		//需要使用k
		for ( int i = 0; i < m; i++ ) {
			line = br.readLine().split(" ");
			long x = Long.parseLong(line[0]), y = Long.parseLong(line[1]);
			dic.add(new Node(x, y, true));
		}
		
		Node temp = dic.get(0);//取出起始点
		temp.cnt = 0;
		temp.k = k;
		
		q.add(temp);
		flag[0] = true;
		
		out:
		while ( !q.isEmpty() ) {//BFS
			Node node = q.get(0);
			q.remove(0);
			
			for ( int i = 1; i < dic.size(); i++ ) {
				if ( !flag[i] && isUnicon(node, dic.get(i), r) ) {//如果能联通
					
					if ( dic.get(i).flag && node.k > 0 ) {//如果需要k
						q.add(new Node(dic.get(i).x, dic.get(i).y, k-1, node.cnt+1));
					} else if ( !dic.get(i).flag ) {//如果不需要k
						q.add(new Node(dic.get(i).x, dic.get(i).y, k, node.cnt+1));
					} else continue;
					
					flag[i] = true;
					if ( i == 1 ) {//到达终点
						System.out.println(node.cnt);
						break out;
					}
					
				}
			}
		}
	}
	
	//判断是否能连接
	public static boolean isUnicon( Node node1, Node node2 , long r ) {
		return (node1.x-node2.x)*(node1.x-node2.x) + (node1.y-node2.y)*(node1.y-node2.y) <= r*r;
	}
}
class Node {
	long x,y,k,cnt;//x,y,k,次数
	boolean flag;//是否需要k
	
	public Node(long x, long y) {
		this.x = x;
		this.y = y;
	}
	public Node(long x, long y, boolean flag) {
		this.x = x;
		this.y = y;
		this.flag = flag;
	}
	public Node(long x, long y, long k, long cnt) {
		this.x = x;
		this.y = y;
		this.k = k;
		this.cnt = cnt;
	}
	@Override
	public String toString() {
		return "x=" + x + ", y=" + y + ", k=" + k + ", cnt=" + cnt + ", flag=" + flag;
	}
	
}

 

中间自己还用了双向BFS去做,但显然我没注意到set的无序性,这样我上面所说的情况就会发生了,得了80分

双向BFS-80分代码

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


public class Main {
	
	static long r = 0;//半径
	static int k = 0;//k值
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] line = br.readLine().split(" ");
		int n = Integer.parseInt(line[0]), m = Integer.parseInt(line[1]), k = Integer.parseInt(line[2]);
		long r = Long.parseLong(line[3]);
		Main.r = r;
		Main.k = k;
		
		HashSet<Node> dic = new HashSet<Node>();//所有点
		HashSet<Node> st = new HashSet<Node>();//起始点
		HashSet<Node> ed = new HashSet<Node>();//终点
		
		//不需要使用k
		for ( int i = 0; i < n; i++ ) {
			line = br.readLine().split(" ");
			long x = Long.parseLong(line[0]), y = Long.parseLong(line[1]);
			if ( i == 0 ) st.add(new Node(x, y, k));//起点
			if ( i == 1 ) ed.add(new Node(x, y, k));//终点
			dic.add(new Node(x, y));//收入所有点
		}
		
		//需要使用k
		for ( int i = 0; i < m; i++ ) {
			line = br.readLine().split(" ");
			long x = Long.parseLong(line[0]), y = Long.parseLong(line[1]);
			dic.add(new Node(x, y, true));
		}
		
		System.out.println(BFS(st, ed, dic, 0));
	}
	
	//判断是否能连接
	public static boolean isUnicon( Node node1, Node node2 ) {
		long a = node1.x, b = node1.y, x = node2.x, y = node2.y;
		if ( Math.pow(Math.abs(x-a), 2) + Math.pow(Math.abs(y-b), 2) <= Math.pow(Main.r, 2) ) return true;
		else return false;
	}
	
	//双向BFS
	public static int BFS( HashSet<Node> st, HashSet<Node> ed, HashSet<Node> dic, int cnt ) {
		if ( st.size() > ed.size() ) {//找小的一边开始
			return BFS(ed, st, dic, cnt);
		}
		
		dic.removeAll(st);//删除开始点
		HashSet<Node> next = new HashSet<Node>();
		
		for (Node node1 : st) {//遍历开始点
			for (Node node2 : dic) {//遍历所有点
				if ( isUnicon(node1, node2) ) {//如果可以连接
					if ( node2.flag && node1.k > 0 ) {//如果需要k且剩余的k大于0
						next.add(new Node(node2.x, node2.y, node1.k-1));
					} else if ( !node2.flag ) {//如果不需要k
						next.add(new Node(node2.x, node2.y, node1.k));
					} else continue;//连不了
					
					if ( ed.contains(node2) ) {//如果另一边用这个点
						for (Node temp : ed) {
							//找到该点且两者的k加起来大于k,注意,是node1的k,不是node2的k
							if ( temp.equals(node2) && node1.k + temp.k >= Main.k) return cnt;
						}
					}
				}
			}
		}
		return BFS(next, ed, dic, cnt+1);
	}
}
class Node {
	long x,y,k;//坐标和k值
	boolean flag;//是否需要k
	
	public Node(long x, long y) {
		this.x = x;
		this.y = y;
	}
	
	public Node(long x, long y, boolean flag) {
		this.x = x;
		this.y = y;
		this.flag = flag;
	}

	public Node(long x, long y, long k) {
		this.x = x;
		this.y = y;
		this.k = k;
	}

	@Override
	public String toString() {
		return "Node [x=" + x + ", y=" + y + ", k=" + k + ", flag=" + flag + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (x ^ (x >>> 32));
		result = prime * result + (int) (y ^ (y >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {//判断是否相等
		Node other = (Node) obj;
		if ( other.x == this.x && other.y == this.y ) return true;
		return false;
	}
}

 

总结:发现自己对BFS的了解只有浅浅的一层,对于双向BFS来说,重点在于set本身的去重,所以每次都是遍历完再删除,这对于set来说是没影响的。但还要注意一个,set的无序性,进去的数据是没有顺序的。
由于这两个特性,在201403-4-无线网络这道题中,哪个数据先访问了点,就占据了该点,后面来的点无法覆盖,这样看着还跟使用flag标记进行是一样的,但由于set的无序性,导致它的数据不在按"不需要k"->"需要k"这样排序,所以就真的会出现自己原先所想的情况,点先被没有k的标记了,后面有k的点来了也无法访问。

总的来说,对于那种访问了马上就能标记的题目,是可以使用set的(坐标类等自己重写hashCode和equals),即双向BFS
对于需要遍历完一遍才能删除的数据(暂且不论数据的顺序),只能采用单向BFS

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值