题目如上,我们可以用邻接矩阵g[][]存储图,如果城市i和城市j有通路的话,令g[i][j]=1
while(m--){
int a,b;
cin>>a>>b;
g[a][b]=g[b][a]=1;//a,b之间有一条路
merge(a,b);
}
merge函数的作用下面会给出
个人认为题目没有说清楚,我之前以为是每一个被攻占的城市单独处理,不影响后面的被攻占的城市,后来发现理解错了。题意是指每一个被攻占的城市是依次执行的,对于每一次攻占的城市,有两种情况(我们假设被攻占的城市相当于被隔离了,所有与他有关的边都去掉了):
情况1、失去他会改变国家连通性
情况2、失去他不会改变国家连通性
看到这种连通性的问题,第一个想到的就是并查集了,刚好此题就是运用并查集
并查集的模板如下:
int find(int x){//找出x的祖宗节点+路径压缩
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
void merge(int a,int b){//合并a,b所在的连通块
a=find(a),b=find(b);
if(a!=b)p[a]=b;
}
首先,记录当前连通块的数量cnt
其次,记录攻占当前城市编号剩余的连通块的数量cnt1
最后,比较cnt和cnt1
并查集记录连通块的数量只需要看祖宗节点为自己本身的节点的个数,代码如下:
int cnt=0;//cnt记录连通块的数量
for(int i=0;i<n;i++)
if(find(i)==i)cnt++;
注意:被攻占后的城市独自占有一个连通块
如果当前城市本来就独自为一个连通块,则cnt1=cnt;如果当前城市在其他连通块里面,有两种情况,cnt1=cnt+1或者cnt1>cnt+1,若cnt1=cnt+1,则去掉该城市其他城市还是联通的,若cnt1>cnt+1,则其他城市不连通了
所以,操作之后,若cnt1>cnt+1,则为情况2,否则为情况1
最后还要注意,如果攻占了该国家的最后一个城市,则输出Game Over.
由于题目已经给出删除的城市不可能有重复的,所以k=n的时候就要输出上述语句了
分析完毕!C++代码如下:
#include<iostream>
using namespace std;
const int N=510,M=5010;
int p[N],g[N][N];
int n,m,k;
int find(int x){//找出x的祖宗节点+路径压缩
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
void merge(int a,int b){//合并两个点a,b
a=find(a),b=find(b);
if(a!=b)p[a]=b;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++)p[i]=i;//初始化每个点各自为一个连通块
while(m--){
int a,b;
cin>>a>>b;
g[a][b]=g[b][a]=1;//a,b之间有一条路
merge(a,b);
}
int cnt=0;//cnt记录原有的连通块的数量
for(int i=0;i<n;i++)
if(find(i)==i)cnt++;
cin>>k;
for(int i=0;i<k;i++){//k个城市被依次攻占
int x,cnt1=0;
cin>>x;
for(int i=0;i<n;i++)p[i]=i;//初始化连通块
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
//对于所有与x有关的边都去掉,重新建立连通块
if(g[i][j]&&(i==x||j==x))g[i][j]=g[j][i]=0;
else if(g[i][j]==1)merge(i,j);
}
//记录攻占x后,剩余的连通块的数量cnt1
for(int i=0;i<n;i++)
if(find(i)==i)cnt1++;
if(cnt1>cnt+1)cout<<"Red Alert: City "<<x<<" is lost!"<<endl;
else cout<<"City "<<x<<" is lost."<<endl;
cnt=cnt1;//更新现有的连通块的数量
}
if(k==n)cout<<"Game Over."<<endl;//由于被攻占的城市无重复,所以k==n表示攻占了所有的城市
return 0;
}