1013 Battle Over Cities (25 分) 计算连通分量个数-并查集或者深搜

It is vitally important to have all the cities connected by highways in a war. If a city is occupied by the enemy, all the highways from/toward that city are closed. We must know immediately if we need to repair any other highways to keep the rest of the cities connected. Given the map of cities which have all the remaining highways marked, you are supposed to tell the number of highways need to be repaired, quickly.

For example, if we have 3 cities and 2 highways connecting city​1​​-city​2​​ and city​1​​-city​3​​. Then if city​1​​ is occupied by the enemy, we must have 1 highway repaired, that is the highway city​2​​-city​3​​.

Input Specification:

Each input file contains one test case. Each case starts with a line containing 3 numbers N (<1000), M and K, which are the total number of cities, the number of remaining highways, and the number of cities to be checked, respectively. Then M lines follow, each describes a highway by 2 integers, which are the numbers of the cities the highway connects. The cities are numbered from 1 to N. Finally there is a line containing Knumbers, which represent the cities we concern.

Output Specification:

For each of the K cities, output in a line the number of highways need to be repaired if that city is lost.

Sample Input:

3 2 3
1 2
1 3
1 2 3

Sample Output:

1
0
0

 

这个题本质就是删除某个给定结点所关联的所有边后,剩余边构成图的连通分量。
方法有两个:1是深度优先搜索(或者广度优先搜索) 2并查集
首先是深搜:
本题需要在深搜上改进一点,因为需要计算连通分量,那么我们可以使用一个函数里面有个for循环,对每个结点进行判断如果没有搜索到就对这个结点进行深搜。
int getLian(){
    for(int i=1;i<=N;i++){
        if(visit[i]==0){
            num++;
            dfs(i,lost);
        }
    }
    return num;
}
下面是深搜函数,sn[pos][i]==1就是找到了这个结点相连的边
void dfs(int pos){
    visit[pos]=1;
    for(int i=1;i<=N;i++){
        if(pos==i)
            continue;
        if(visit[i]==1)
            continue;
        if(sn[pos][i]==1){
            dfs(i,lost);
        }
    }
}
总体还是比较简单的,但是本题还需要提前删除结点,那么我们只需要对上面两个函数修改一下就好,删除结点 也就是在getline函数中遇到需要删除的结点不查找就好了,在dfs函数中也是直接跳过不深搜就好了,这样记录边的二维数组sn也就不需要修改了。
int getLian(int lost){
    for(int i=1;i<=N;i++){
        if(i==lost)
        continue;
        if(visit[i]==0){
            num++;
            dfs(i,lost);
        }
    }
    return num;
}
void dfs(int pos,int lost){
    visit[pos]=1;
    for(int i=1;i<=N;i++){
        if(pos==i)
            continue;
        if(i==lost)
            continue;
        if(visit[i]==1)
            continue;
        if(sn[pos][i]==1){
            dfs(i,lost);
        }
    }
}
其他就没什么了上代码

#include <iostream>
#include <cstdio>
#include <string.h>
#include <vector>
#include <algorithm>
#include <cmath>
#include <queue>
#include <sstream>
#include <unordered_map>
#include <set>
#include <climits>

using namespace std;
int N;
int num=0;
int visit[1000]={0};
int sn[1000][1000]={0};
void dfs(int pos,int lost){
    visit[pos]=1;
    for(int i=1;i<=N;i++){
        if(pos==i)
            continue;
        if(i==lost)
            continue;
        if(visit[i]==1)
            continue;
        if(sn[pos][i]==1){
            dfs(i,lost);
        }
    }
}
int getLian(int lost){
    for(int i=1;i<=N;i++){
        if(i==lost)
        continue;
        if(visit[i]==0){
            num++;
            dfs(i,lost);
        }
    }
    return num;
}
int main() {
   int M,K;
   //cin>>N>>M>>K;
   scanf("%d %d %d",&N,&M,&K);

   for(int i=0;i<M;i++){
    int a,b;
    scanf("%d",&a);
        scanf("%d",&b);
        sn[a][b]=1;
        sn[b][a]=1;
   }
   int lost[K];
   for(int i=0;i<K;i++){
   // cin>>lost[i];
     scanf("%d",&lost[i]);
   }
   for(int i=0;i<K;i++){
        fill(visit,visit+1000,0);
        num=0;
        cout<<getLian(lost[i])-1<<endl;
   }
    
    return 0;
}

下面并查集的方法
首先是并查集的理解,这里首推 武林版本的解释,分享一个博客,我当初就是在这里取经的https://blog.csdn.net/wang814597438/article/details/79649351

关键代码

int pre[1000]={0}; //记录一个结点所在联通分量的老大结点
int Find(int x)
    {
        int r=x;  //寻找老大 最差就是自己就是老大了   
        while(r!=pre[r])
            r=pre[r];

//把寻找路径上所有结点老大固定为一个最后寻找到的老大结点
//下面一步也称之为路径压缩
        int i=x,j;
        while(pre[i]!=r)
        {
            j=pre[i];
            pre[i]=r;
            i=j;
        }
        return r;
    }
    void mix(int x,int y)//合并
    {   
        int fx=Find(x),fy=Find(y);
        if(fx!=fy)
        {
            pre[fy]=fx;
        }
    }

上面两个函数会产生一个问题:
举个例子有两个边 比如 边1 2  和边3 2
1前驱是1  2前驱是2 第一次mix(1,2)后, 1前驱是1 2前驱是1(就是后面结点的老大自认不如,不再感觉最厉害了,认了前面结点老大为首领了)
然后mix(3,2)  1的前驱是3  3的前驱是自己,但是2的前驱还是1
其实就是那么y 到 y之前老大结点fy 之间结点的还是认可之前的老大也就是fy,这些结点其实也需要更改,因为fy都认了新的老大了。所以需要在最后,对每个结点进行一次 Find函数就好了,让每个结点从新找找老大
    for(int q=1;q<=N;q++){
            if(q==lost[i])
                continue;
            Find(q);//有了这个才能对,否则会出现 上面的情况

            sett.insert(pre[q]);
        }

  代码:

#include <iostream>
#include <cstdio>
#include <string.h>
#include <vector>
#include <algorithm>
#include <cmath>
#include <queue>
#include <sstream>
#include <unordered_map>
#include <set>
#include <climits>

using namespace std;

int pre[1000]={0};
int Find(int x)
	{
	    int r=x;  //寻找前驱结点,最差就是自身了
	    while(r!=pre[r])
	        r=pre[r];

	      //把路径上所有连通图结点前驱固定为一个
	    int i=x,j;
	    while(pre[i]!=r)
	    {
	        j=pre[i];
	        pre[i]=r;
	        i=j;
	    }
	    return r;
	}
	void mix(int x,int y)
	{   //fy的前驱不再是自身了,那么y 到fy结点之间也需要更改
	    int fx=Find(x),fy=Find(y);
	    if(fx!=fy)
	    {
	        pre[fy]=fx;
	    }
	}
int main() {
   int N,M,K;
   //cin>>N>>M>>K;
   scanf("%d %d %d",&N,&M,&K);
   int sn[M][2];
   for(int i=0;i<M;i++){
    //cin>>sn[i][0]>>sn[i][1];
    scanf("%d",&sn[i][0]);
        scanf("%d",&sn[i][1]);
   }
   int lost[K];
   for(int i=0;i<K;i++){
   // cin>>lost[i];
     scanf("%d",&lost[i]);
   }
   if(N==1){
    cout<<0;
    return 0;
   }
   for(int i=0;i<K;i++){

        fill(pre,pre+1000,0);
        for(int q=1;q<=N;q++){
            if(q==lost[i])
                continue;
            pre[q]=q;
        }
        for(int q=0;q<M;q++){
            if(sn[q][0]==lost[i]||sn[q][1]==lost[i]){
                continue;
            }
            mix(sn[q][0],sn[q][1]);
        }
        set<int> sett;
        for(int q=1;q<=N;q++){
            if(q==lost[i])
            	continue;
            Find(q);//#include <iostream>
#include <cstdio>
#include <string.h>
#include <vector>
#include <algorithm>
#include <cmath>
#include <queue>
#include <sstream>
#include <unordered_map>
#include <set>
#include <climits>

using namespace std;

int pre[1000]={0};
int Find(int x)
	{
	    int r=x;  //寻找前驱结点,最差就是自身了
	    while(r!=pre[r])
	        r=pre[r];

	      //把路径上所有连通图结点前驱固定为一个
	    int i=x,j;
	    while(pre[i]!=r)
	    {
	        j=pre[i];
	        pre[i]=r;
	        i=j;
	    }
	    return r;
	}
	void mix(int x,int y)
	{   //fy的前驱不再是自身了,那么y 到fy结点之间也需要更改
	    int fx=Find(x),fy=Find(y);
	    if(fx!=fy)
	    {
	        pre[fy]=fx;
	    }
	}
int main() {
   int N,M,K;
   //cin>>N>>M>>K;
   scanf("%d %d %d",&N,&M,&K);
   int sn[M][2];
   for(int i=0;i<M;i++){
    //cin>>sn[i][0]>>sn[i][1];
    scanf("%d",&sn[i][0]);
        scanf("%d",&sn[i][1]);
   }
   int lost[K];
   for(int i=0;i<K;i++){
   // cin>>lost[i];
     scanf("%d",&lost[i]);
   }
   if(N==1){
    cout<<0;
    return 0;
   }
   for(int i=0;i<K;i++){

        fill(pre,pre+1000,0);
        for(int q=1;q<=N;q++){
            if(q==lost[i])
                continue;
            pre[q]=q;
        }
        for(int q=0;q<M;q++){
            if(sn[q][0]==lost[i]||sn[q][1]==lost[i]){
                continue;
            }
            mix(sn[q][0],sn[q][1]);
        }
        set<int> sett;
        for(int q=1;q<=N;q++){
            if(q==lost[i])
            	continue;
            Find(q);//有了这个才能对,否则会出现 上面的情况
            sett.insert(pre[q]);
        }
//         set<int>::iterator it;
//        for(it=sett.begin(); it!=sett.end(); it++)
//            cout<<*it<<"\t";
        //cout<<pre[145]<<' '<<pre[235];
        cout<<sett.size()-1<<endl;
   }
    return 0;
}

//并查集 
            sett.insert(pre[q]);
        }
//         set<int>::iterator it;
//        for(it=sett.begin(); it!=sett.end(); it++)
//            cout<<*it<<"\t";
        //cout<<pre[145]<<' '<<pre[235];
        cout<<sett.size()-1<<endl;
   }
    return 0;
}

//并查集 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值