危险系数(又名:风险度量)(并查集)

14 篇文章 0 订阅
12 篇文章 0 订阅

问题描述
抗日战争时期,冀中平原的地道战曾发挥重要作用。

地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。

我们来定义一个危险系数DF(x,y):

对于两个站点x和y (x != y), 如果能找到一个站点z,当z被敌人破坏后,x和y不连通,那么我们称z为关于x,y的关键点。相应的,对于任意一对站点x和y,危险系数DF(x,y)就表示为这两点之间的关键点个数。

本题的任务是:已知网络结构,求两站点之间的危险系数。

输入格式
输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,通道数;

接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条通道;

最后1行,两个数u,v,代表询问两点之间的危险系数DF(u, v)。

输出格式
一个整数,如果询问的两点不连通则输出-1.
样例输入
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
样例输出
2
其实这个题和风险度量是一模一样的,给的数据也是一样的,就题的背景不一样…
这道题用并查集做来比较直接也非常好想,之前想复杂了,一直以为用并查集会超时(因为需要去枚举每个点是否是割点),最后还是试一试,竟然过了,可能是数据比较水。
代码(296ms):

import java.util.Scanner;

public class Main 
{   
    static int start,end;//题设要求判断的两个点,
    static int pre[];//并查集的数据结构
    public static void main(String[] args) 
    {
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        int count=0;
        pre=new int[n+1];
        Edge ee[]=new Edge[m];
        for(int i=1;i<=n;i++)
            pre[i]=i;//初始化,每个元素代表自己
        int x1,x2;
        for(int i=0;i<m;i++)
        {
            x1=sc.nextInt();
            x2=sc.nextInt();
            ee[i]=new Edge(x1,x2);//因为下面的判断割点要枚举边,所以需要保存每条边的信息
            int t1=find(x1);//寻找他们的代表元
            int t2=find(x2);
            if(t1!=t2)//如果不相同,就将其合并
            {
                pre[t1]=t2;
            }
        }//这个第一次做整个图的并查集是是为了先判断,题设两个点是否连通。
        start=sc.nextInt();
        end=sc.nextInt();
        if(find(start)!=find(end))//如果不连通,直接输出,下面的代码页就不用执行了
        {
            System.out.println(-1);
            return;
        }
        for(int i=1;i<=n;i++)//如果能执行接下来的代码,说明就是是连通的,找出有多少个割点//枚举1到n的点除去start 和end
        {
            if(i==start||i==end)continue;//开始和结束点不考虑
            for(int j=1;j<=n;j++)
                pre[j]=j;//每次枚举一个点都要初始化
            for(int j=0;j<m;j++)
            {
                if(ee[j].x1==i)continue;//只要和这个点有关的边的,就相当于是不存在的,直接跳过,然后接下来在判断 start和end的代表元是否相等
                if(ee[j].x2==i)continue;
                int t1=find(ee[j].x1);
                int t2=find(ee[j].x2);
                if(t1!=t2)
                {
                    pre[t1]=t2;
                }
            }
            if(find(start)!=find(end))//如果不相等,说明有一个割点
            {
                count++;
            }
        }
        System.out.println(count);
    }
    static int find(int x)//并查集
    {
        return pre[x]==x?x:(pre[x]=find(pre[x]));
    }
}

class Edge//边
{
    int x1,x2;
    Edge(int x1,int x2)
    {
        this.x1=x1;
        this.x2=x2;
    }
}

是否真的每个点都需要去枚举么?实际上,以上的代码不是我首先想到的,因为我一直在犹豫效率问题。其实我想到,从start到end,比如题设给的样例数据,我们随便找一条1到6的的路径1->3->4->5->6。1和6之间有3个点,那么我们可以说此时DF(1,6)绝对小于等于3,并且可能的割点必然也在3、4、5之中。反证法,假如还存一个割点不存在此路径,设为x点,那么,删去x点则1就不能到达6,但是已知1->3->4->5->6这条路径使1到6连通,所以x不可能为割点。
以上得出的结论非常有用,那么我们只要随便找出一条路径,并记录中间过程的点,然后从中取枚举(上例边只要在3、4、5中去枚举即可)。当然可能的最坏情况是所有的点全部都遍历了,但是,应该不会这么倒霉吧hh。
去找一条路径,很天然的,当然是深搜啦(DFS)
当然因为要深搜,所有就需要去建图,这里我用邻接表
优化(203ms):

import java.util.ArrayList;
import java.util.Scanner;

public class Main 
{   
    static ArrayList<Integer> list=new ArrayList<>();//这个是用来存储一条可达路径上所需要枚举的点
    static int start,end;
    static ArrayList<Integer> g[];//图
    static boolean sign=false;//如果找到一条路径,就置为true;
    static int visited[];//判断是否已经访问过
    static int pre[];
    public static void main(String[] args) 
    {
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        int count=0;
        pre=new int[n+1];
        Edge ee[]=new Edge[m];
        visited=new int[n+1];
        g=(ArrayList<Integer>[])new ArrayList[n+1];
        for(int i=1;i<=n;i++)
            pre[i]=i;
        int x1,x2;
        for(int i=0;i<m;i++)
        {
            x1=sc.nextInt();
            x2=sc.nextInt();
            ee[i]=new Edge(x1,x2);

            if(g[x1]==null)g[x1]=new ArrayList<>();
            if(g[x2]==null)g[x2]=new ArrayList<>();
            g[x1].add(x2);
            g[x2].add(x1);//在做并查集的同时建图
            int t1=find(x1);
            int t2=find(x2);
            if(t1!=t2)
            {
                pre[t1]=t2;
            }
        }
        start=sc.nextInt();
        end=sc.nextInt();
        if(find(start)!=find(end))
        {
            System.out.println(-1);
            return;
        }
        dfs(start);//因为是判断了两个点连通才做的dfs,所以必然会找到一条路径,而且要注意,第一个进入list的点一定是start,所以这就是为什么要从list的1开始而不是0
        for(int i=1;i<list.size();i++)//看,这里就只需要来list里面的元素去枚举即可
        {
            int x=list.get(i);
            for(int j=1;j<=n;j++)
                pre[j]=j;
            for(int j=0;j<m;j++)
            {
                if(ee[j].x1==x)continue;
                if(ee[j].x2==x)continue;
                int t1=find(ee[j].x1);
                int t2=find(ee[j].x2);
                if(t1!=t2)
                {
                    pre[t1]=t2;
                }
            }
            if(find(start)!=find(end))
            {
                count++;
            }
        }
        System.out.println(count);
    }
    static void dfs(int k)
    {
        if(k==end)
        {
            sign=true;//说明找到一条路,这是找到的标志量,
            return;
        }
        list.add(k);//加入路径的一个可能的点
        visited[k]=1;//然后置1,说明访问过了
        for(int i=0;i<g[k].size();i++)
        {
            if(visited[g[k].get(i)]==1)continue;
            dfs(g[k].get(i));
            if(sign)return;//如果找到了,提前终止,这个代码不难,好好想想
        }
        visited[k]=0;
        list.remove(list.size()-1);//如果回调,说明这个点到达不了,移除这个点,一般情况下,能到达这步的概率应该比较小
    }
    static int find(int x)
    {
        return pre[x]==x?x:(pre[x]=find(pre[x]));
    }
}

class Edge
{
    int x1,x2;
    Edge(int x1,int x2)
    {
        this.x1=x1;
        this.x2=x2;
    }
}

以上代码相比于第一个,多了建图和dfs过程,实际上,这才我写的第一个代码,而文章的第一个代码,是为了比较效率才写的,事实证明也的确如此,快了将近90ms,但是两个代码都ac了。

peace&love

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值