欢迎访问我的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