问题描述
抗日战争时期,冀中平原的地道战曾发挥重要作用。
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数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