题目真心分析不出来。看了白书才明白,不过有点绕脑。
容易想到,把题目给的不相邻的关系,利用矩阵,反过来建图。既然是全部可行的关系,那么就应该能画出含奇数个点的环。求环即是求双连通分量:找出所有的双连通分量,只要分量中的点数是奇数,则排除“must be expelled”的可能性。
判环上的点数用二分图,这个我都想了半天= =,如果是奇数个点,明摆着多出来的一个点放到哪个集合都会与集合内的点连边(这个找三个点自己画画试试就明白了)。0、1染色,本人喜欢用bfs,递归什么的其实都可以。
我自己用缩点做的,果断wa到吐。举个例子:五个点,{1,2,3}{3,4,5},这样3就是一个割顶了,缩点的话是在遍历完邻接表之后,再判断low[u]==dfn[u],如此5个点就缩成了一个点,即一个分量,虽然这个分量包含奇数个点,输出同样是0,但与我们的思路是不一样的。实际情况是分成两个分量,每个分量有三个点,割顶3同时出现在两个分量中。然后想着改进,当把割顶弹出栈后,再弹入,搞啊搞,还是算了,学着白书上用边搞。
1 #include<stdio.h> 2 #include<string.h> 3 #include<vector> 4 #include<stack> 5 #include<algorithm> 6 using namespace std; 7 8 const int MAXN=1111; 9 10 struct EDGE{ 11 int u,v; 12 EDGE(){} 13 EDGE(int _u,int _v):u(_u),v(_v){} 14 }; 15 16 struct Edge{ 17 int v,next; 18 Edge(){} 19 Edge(int _v,int _next):v(_v),next(_next){} 20 }edge[MAXN*MAXN]; 21 22 int mp[MAXN][MAXN],tol,head[MAXN]; 23 int low[MAXN],dfn[MAXN],bccno[MAXN],iscut[MAXN],TT,bcc_cnt; 24 int que[MAXN],color[MAXN]; 25 int sign[MAXN]; 26 27 vector<int >bcc[MAXN]; 28 stack<EDGE >stk; 29 30 void init() 31 { 32 tol=0; 33 memset(head,-1,sizeof(head)); 34 } 35 36 void add(int u,int v) 37 { 38 edge[tol]=Edge(v,head[u]); 39 head[u]=tol++; 40 } 41 42 void dfs(int u,int fa) 43 { 44 low[u]=dfn[u]=++TT; 45 int son=0; 46 for(int i=head[u];i!=-1;i=edge[i].next) 47 { 48 int v=edge[i].v; 49 EDGE e=EDGE(u,v); 50 if(!dfn[v]){ 51 stk.push(e); 52 son++; 53 dfs(v,u); 54 low[u]=min(low[v],low[u]); 55 if(low[v]>=low[u]){ 56 iscut[u]=1; 57 bcc_cnt++; 58 bcc[bcc_cnt].clear(); 59 while(1) 60 { 61 EDGE x=stk.top(); 62 stk.pop(); 63 if(bccno[x.u]!=bcc_cnt){ 64 bcc[bcc_cnt].push_back(x.u); 65 bccno[x.u]=bcc_cnt; 66 } 67 if(bccno[x.v]!=bcc_cnt){ 68 bcc[bcc_cnt].push_back(x.v); 69 bccno[x.v]=bcc_cnt; 70 } 71 if(x.u==u&&x.v==v) 72 break; 73 } 74 } 75 }else if(dfn[v]<dfn[u]&&v!=fa){ 76 stk.push(e); 77 low[u]=min(low[u],dfn[v]); 78 } 79 } 80 if(fa<0&&son==1) 81 iscut[u]=0; 82 } 83 84 void find_bcc(int n) 85 { 86 memset(low,0,sizeof(low)); 87 memset(dfn,0,sizeof(dfn)); 88 memset(bccno,0,sizeof(bccno)); 89 memset(iscut,0,sizeof(iscut)); 90 91 TT=bcc_cnt=0; 92 93 for(int i=1;i<=n;i++) 94 if(!dfn[i]) 95 dfs(i,-1); 96 } 97 98 bool bfs(int x,int fa) 99 { 100 int l,r; 101 l=r=0; 102 que[r++]=x; 103 while(l<r) 104 { 105 int u=que[l++]; 106 for(int i=head[u];i!=-1;i=edge[i].next) 107 { 108 int v=edge[i].v; 109 if(bccno[v]!=fa) 110 continue ; 111 if(color[v]==-1){ 112 color[v]=color[u]^1; 113 que[r++]=v; 114 }else if(color[v]==color[u]) 115 return false; 116 } 117 } 118 return true; 119 } 120 121 void Bjudge() 122 { 123 memset(sign,0,sizeof(sign)); 124 for(int i=1;i<=bcc_cnt;i++) 125 { 126 memset(color,-1,sizeof(color)); 127 for(int j=0;j<bcc[i].size();j++) 128 bccno[bcc[i][j]]=i; 129 int u=bcc[i][0]; 130 color[u]=0; 131 if(!bfs(u,i)){ 132 for(int j=0;j<bcc[i].size();j++) 133 sign[bcc[i][j]]=1; 134 } 135 } 136 } 137 138 int main() 139 { 140 int n,m; 141 int a,b; 142 while(~scanf("%d%d",&n,&m)!=EOF) 143 { 144 if(!n&&!m) 145 break; 146 memset(mp,0,sizeof(mp)); 147 for(int i=0;i<m;i++) 148 { 149 scanf("%d%d",&a,&b); 150 mp[a][b]=mp[b][a]=1; 151 } 152 153 init(); 154 for(int i=1;i<=n;i++) 155 { 156 for(int j=i+1;j<=n;j++) 157 { 158 if(!mp[i][j]){ 159 add(i,j); 160 add(j,i); 161 } 162 } 163 } 164 165 find_bcc(n); 166 167 Bjudge(); 168 169 int ans=0; 170 for(int i=1;i<=n;i++) 171 if(!sign[i]) 172 ans++; 173 printf("%d ",ans); 174 } 175 return 0; 176 } 177 /* 178 5 4 179 1 4 180 1 5 181 2 4 182 2 5 183 184 6 8 185 1 4 1 5 1 6 186 2 4 2 5 2 6 187 3 4 3 5 188 */
最近做了几道connectivity的题目,总结一下。
关于割顶、桥、双连通、边双连通,以及强连通处理方式有其相似性,关键都与时间戳有关。
其中,割顶->双连通 是low[v]>=dfn[u],桥->边双连通 是low[v]>dfn[u],只是一个等号的差别,其他处理基本相同;而强连通总是伴随着缩点 low[u]==dfn[u](这个一般是做边标记edge[].vis,这样即使是无向图也可以以edge[i^1].vis标记掉,而不影响重边的情况)。事实上,如果不考虑具体的桥,对边-双连通分量的划分就是在做无向图上的缩点操作。
这三个判定条件的位置也有不同。缩点是在遍历完u的邻接表之后,用每个low[v]的值更新low[u],并且u本身不会连到祖先去(这一点很重要),则是一个环,可以缩掉;在无向图中,判断双连通分量,也就是割顶(边-双连通分量&桥 一样),是每遍历一个孩子v,就要判断:low[v]>=dfn[u],只要点u的孩子所能到达的最大值不超过u,那么u就是割顶(删除u后,该子树独立),当然,u的每一个孩子v都可以是被 割顶u 分离,注意u本身是可以与它的祖先连接的!!