L2-013 红色警报 - Java

战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城市并不改变其他城市之间的连通性,则不要发出警报。

输入格式:

输入在第一行给出两个整数N(0 < N ≤ 500)和M(≤ 5000),分别为城市个数(于是默认城市从0到N-1编号)和连接两城市的通路条数。随后M行,每行给出一条通路所连接的两个城市的编号,其间以1个空格分隔。在城市信息之后给出被攻占的信息,即一个正整数K和随后的K个被攻占的城市的编号。

注意:输入保证给出的被攻占的城市编号都是合法的且无重复,但并不保证给出的通路没有重复。

输出格式:

对每个被攻占的城市,如果它会改变整个国家的连通性,则输出Red Alert: City k is lost!,其中k是该城市的编号;否则只输出City k is lost.即可。如果该国失去了最后一个城市,则增加一行输出Game Over.

输入样例:

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

输出样例:

City 1 is lost.
City 2 is lost.
Red Alert: City 0 is lost!
City 4 is lost.
City 3 is lost.
Game Over.

 思路分析:

 首先要明白两个概念:

连通分量:一个图中有n个子图,这n个子图之间是不连通的,但其内部都是连通的,那么这n个子图都是原图的连通分量,也叫做极大连通子图。
割点:如果删除图中的一个点,原图的连通分量就增加的话,那么这个删除的点就是原图的一个割点。
本题就是判断删除的一个城市是否为割点,也就是说判断删除一个城市是否会增加连通分量,若增加了连通分量,那么这个城市就报红色警戒。但要注意,在计算连通分量个数时,要在去掉被攻击的城市的图上计算,不能在原图上进行计算。

思路并不是很复杂,直接上代码吧。

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;

public class Main {
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer st = new StreamTokenizer(reader);
    static PrintWriter out = new PrintWriter(System.out);

    //读取整数
    public static int readInt() throws IOException {
        st.nextToken();
        return (int)st.nval;
    }

    //读取字符串
    public static String readString() throws IOException {
        st.nextToken();
        return st.sval;
    }

    //读取一整行
    public static String readLine() throws IOException {
        return reader.readLine();
    }

    public static void main(String[] args) throws IOException {
        int N = readInt();
        int M = readInt();
        int[][] matrix = new int[N][N];
        for(int i=0;i<M;i++){
            int a = readInt();
            int b = readInt();
            matrix[a][b] = 1;
            matrix[b][a] = 1;
        }
//        for(int i=0;i<N;i++){
//            out.println(Arrays.toString(matrix[i]));
//        }
        //定义一个变量记录图的连通分量
        int components = getComponents(matrix);
        //定义一个集合用于存储被攻击的城市
        ArrayList<Integer> attackedCity = new ArrayList<>();
        int K = readInt();
        for(int i=0;i<K;i++){
            int n = readInt();
            attackedCity.add(n);
            //删除当前城市
            for(int j=0;j<N;j++){
                matrix[j][n] = 0;
                matrix[n][j] = 0;
            }
//            for(int x=0;x<N;x++){
//                out.println(Arrays.toString(matrix[x]));
//            }
            //再次获取删除城市后的连通分量
            int temp = getComponents(transformMatrix(matrix,attackedCity));
//            out.println(temp);
            if(temp > components){
                //如果连通分量增加,说明删除的城市为割点,红色警戒
                out.println("Red Alert: City " + n + " is lost!");
                out.flush();
            }else{
                //否则就不报红色警戒
                out.println("City " + n + " is lost.");
                out.flush();
            }
            //将新的连通分量个数赋值给components
            components = temp;
        }
        if(K >= N){
            out.println("Game Over.");
            out.flush();
        }
        //最好不要在函数的最后才刷新缓冲区,因为如果一次性要输出的太多,而数据都堆积在缓冲区,且没有及时刷新,
        //会导致后来的数据把前面的数据覆盖,从而导致打印出错。
//        out.flush();
        out.close();
    }

    public static int getComponents(int[][] matrix){
        if(matrix == null)      return 0;
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        //定义一个集合用于存储还未被划分到某个极大连通子图的节点
        ArrayList<Integer> list = new ArrayList<>();
        int N = matrix.length;
        for(int i=0;i<N;i++){
            list.add(i);
        }
        for(int i=0;i<N;i++){
            //如果list中还包含i,说明i当前还没有添加到任意个极大联通子图中
            if(list.contains(i)){
                //获取包含i节点的极大连通子图
                getZiTuFromNode(i,-1,i,list,result,matrix);
            }
        }
        return result.size();
    }

    //获取包含i节点的极大连通子图
    public static void getZiTuFromNode(int start,int previous,int now,ArrayList<Integer> list,
                                       ArrayList<ArrayList<Integer>> result,int[][] matrix){
        //previous为-1说明没有上一个节点,说明当前节点为开始节点,递归刚开始
        //如果previous不为-1,且当前节点为开始节点,说明构成了环,直接退出函数
        if(previous != -1 && now == start)  return;
        int N = matrix.length;
        if(previous == -1){
            ArrayList<Integer> newList = new ArrayList<>();
            result.add(newList);
        }
        //ArrayList中的getLast方法是jdk21后新加的ArrayList方法,返回集合的最后一个元素,若无元素,则报错
        // ArrayList<Integer> tempList = result.getLast();
        ArrayList<Integer> tempList = result.get(result.size()-1);
        //然后判断当前节点是否在tempList中已经存在
        if(tempList.contains(now))  return;
        //如果不存在,则添加到该集合中,并从list中删除该节点
        tempList.add(now);
        list.remove(Integer.valueOf(now));  //删除的时候要包装为Integer类,否则会按照索引删除
        //然后遍历now节点连通的所有除自己以外的其它节点,对遍历的每个节点执行此函数
        for(int i=0;i<N;i++){
            if(i != now && matrix[now][i] != 0){
                getZiTuFromNode(start,now,i,list,result,matrix);
            }
        }
    }

    //矩阵转换,去掉原矩阵中不需要的行和列,得到新的矩阵,行和列的索引必须要对应
    //因为要在去掉被攻击的城市的图上统计连通分量个数,所以要转换矩阵
    public static int[][] transformMatrix(int[][] matrix,ArrayList<Integer> list){
        int len = matrix.length;
        int size = list.size();
        if(len == size)     return null;
        int[][] tempMatrix = new int[len - size][len - size];
        int tx = 0;
        int ty = 0;
        for(int x=0;x<len;x++){
            if(!list.contains(x)){
                ty = 0;
                for(int y=0;y<len;y++){
                    if(!list.contains(y)){
                        tempMatrix[tx][ty] = matrix[x][y];
                        ty++;
                    }
                }
                tx++;
            }
        }
//        for(int x=0;x<len;x++){
//            out.println(Arrays.toString(matrix[x]));
//            out.flush();
//        }
//        for(int x=0;x<len-size;x++){
//            out.println(Arrays.toString(tempMatrix[x]));
//            out.flush();
//        }
        return tempMatrix;
    }
}

         Java运行的话2和4测试点会超时,虽然算法不是很好(毕竟本人也是小白),但感觉思路还是比较好理解的,如果有大佬有更好的方法,希望可以分享出来!

          大家一起进步!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值