题目大意:有一些摄像头,监视着一些位置,每个摄像头有自己的位置,如果这个摄像头的位置被其他摄像头监视着,那么它就不能被拆除,拆除一个摄像头之后就不能监视它原来监视的位置,问可否拆完全部摄像头。
这道题做法很多,这里提供两种做法,一种是普遍使用的做法,一种是我自己的做法(我的做法。。可能比较烦,但思路类似,请放心食用!)
做法1:
对于这道题,很容易可以想到,若摄像头A监视着摄像头B,那么A就向B连一条边,每一次只能拆除入度为0的点,也就是没有被监视着的摄像头,那么也就是拓扑排序,如果这个图有拓扑排序,那么一定可以拆完所有摄像头,如果没有,则输出最后剩下几个摄像头即可,这种大家普遍使用的做法这里就不细讲了,这题拓扑做法的题解很多,很容易找,时间复杂度为O(n+e)。
做法2:
这样做可能有点大材小用。。但是不要紧!条条道路通罗马嘛。
图就按上面的那样建,建完之后,我们首先考虑,什么样的摄像头拆不了?容易想到,在环里面的摄像头是拆不了的,因为你无法从里面任何一个摄像头入手,换个说法,在环里面每个点是可以走出去然后再走回来的,那么这就等价于环里面的每个摄像头都监视着自己,所以拆不了,那么就可以想到用强联通来搞,把图里面的全部强联通分量进行缩点,然后考虑缩点后的图,从每个入度为0的点出发,如果这个点不是一个环(这个点是一个环指的是它是自环或者它是缩成的点),那么ans++,然后继续遍历它连着的点即可,时间复杂度为O(n+e+n)。
代码如下:
#include <cstdio>
#include <cstring>
int n,len=0,lenn=0;
struct node{int x,y,next;};//邻接表
node e[10010],ee[10010];
int first[110];
int a[510],shuru[100010],t=0;
bool huan[110];
void buildroad(int x,int y)
{
len++;
e[len].x=x;
e[len].y=y;
e[len].next=first[x];
first[x]=len;
}
int ans,dfn[110],low[110],tot=0,zhan[110],belong[110],cnt=0,size[110];
bool v[110];
int minn(int x,int y){return x<y?x:y;}
void dfs(int x)
{
if(x==0)return;
zhan[++t]=x;//栈
v[x]=true;//标记是否在栈内
dfn[x]=low[x]=++tot;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(dfn[y]==0)
{
dfs(y);
if(low[y]<low[x])low[x]=low[y];
}
else if(v[y])low[x]=minn(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
int xx;cnt++;//cnt表示强联通分量个数
do
{
xx=zhan[t--];
v[xx]=false;
belong[xx]=cnt;//belong[i]表示i所在的强联通分量的编号
size[cnt]++;
}while(xx!=x);
}
}
int du[110];
void run(int x)
{
if(!huan[x]&&du[x]==0)//假如x不是一个环,并且它的入度为零
{
ans++;//拆除的摄像头数量+1
for(int i=first[x];i;i=ee[i].next)
{
du[ee[i].y]--;//当x被摧毁,它所连向的点的入度都要-1
run(ee[i].y);
}
}
}
int main()
{
scanf("%d",&n);ans=0;
memset(a,-1,sizeof(a));//a[i]表示i位置的摄像头的编号,没有就是-1
for(int i=1;i<=n;i++)//摄像头按1~n编号,用数组shuru存下输入,为什么要这样处理仔细想想就明白了
{
scanf("%d %d",&shuru[t+1],&shuru[t+2]);
t+=2;
a[shuru[t-1]]=i;//标记这个位置的摄像头的编号为i
int y=shuru[t];
while(y--)scanf("%d",&shuru[++t]);
}
memset(first,0,sizeof(first));
for(int i=0;i<t;)
{
int x=a[shuru[++i]],y=shuru[++i];
while(y--)if(a[shuru[++i]]!=-1)buildroad(x,a[shuru[i]]);//假如摄像头x监视的这个位置有摄像头,就连一条边
}
memset(dfn,0,sizeof(dfn));t=0;
memset(v,false,sizeof(v));
memset(size,0,sizeof(size));//size[i]表示i这个强联通分量的大小(包含多少个点)
for(int i=1;i<=n;i++)//强联通
if(dfn[i]==0)dfs(i);
memset(first,0,sizeof(first));//记得清空,循环使用
memset(du,0,sizeof(du));//记录每个点(缩完后的途中)的入度
memset(huan,false,sizeof(huan));
for(int i=1;i<=len;i++)
{
int x=e[i].x,y=e[i].y;
du[belong[y]]++;
if(belong[x]==belong[y])huan[belong[x]]=true;//假如有一条边上的两个点处于同一个强联通分量,那么标记这个强联通分量为环
if(belong[x]!=belong[y])//假如不在同一个强联通分量,就建一条边
{
ee[++lenn].x=belong[x];
ee[lenn].y=belong[y];
ee[lenn].next=first[belong[x]];
first[belong[x]]=lenn;
}
}
for(int i=1;i<=cnt;i++)
if(!du[i])run(i);//假如没有入度,就从i开始遍历一遍
if(ans!=n)printf("%d",n-ans);
else printf("YES");
}
往下看有福利
其实之前我用一个WA的代码AC了这题,上面那个是改过的,详情请见这里