POJ 3352&&3177 (割边 && 边双连通分量)

题目:加几条边才能使原图变成边双连通分量。 [求割边]对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。根据定义,则有:Low(u)=Min {DFS(u),DFS(v)|(u,v)为后向边(等价于DFS(v)<DFS(u)且v不为u的父亲节点),Low(v)|(u,v)为树枝边} [条件]一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v) [求点双连通分支]只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。 [注]重边对求割边和边连通分量有影响,此题无重边,重边对求割边和边连通分量有影响,重边处理方法是在DFS时标记每条边及其反向边是否被访问过(即按边访问),而不是判断顶点(按点访问)。 POJ 3352(无重边):
#include 
 
   
    
  
#include 
  
    
      #include 
     
       #include 
      
        #include 
       
         #include 
        
          #include 
         
           #include 
          
            #include 
            #include 
            
              #include 
             
               #include 
              
                #define MID(x,y) ((x+y)>>1) #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; const int MAXV = 5005; const int MAXE = 20005; struct node{ int u, v; int next; int opp; //把一个无向边拆成两个有向边,对应反向边标号 }arc[MAXE]; int cnt, head[MAXV]; void init(){ cnt = 0; mem(head, -1); return ; } void add(int u, int v){ arc[cnt].u = u; arc[cnt].v = v; arc[cnt].next = head[u]; arc[cnt].opp = cnt + 1; head[u] = cnt ++; arc[cnt].u = v; arc[cnt].v = u; arc[cnt].next = head[v]; arc[cnt].opp = cnt - 1; head[v] = cnt ++; return ; } int id, dfn[MAXV], low[MAXV]; int bridge[MAXE]; //标记该边是不是桥 void tarjan(int u, int father){ dfn[u] = low[u] = ++id; for (int i = head[u]; i != -1; i = arc[i].next){ int v = arc[i].v; if (v == father) continue; //if (dfn[v] < dfn[u]){ if (!dfn[v]){ tarjan(v, u); low[u] = min(low[u], low[v]); if (dfn[u] < low[v]){ bridge[i] = 1; bridge[arc[i].opp] = 1; } } else{ low[u] = min(low[u], dfn[v]); } //} } } int bcc_num; bool vis[MAXV]; bool vis_arc[MAXE]; //一条边无向边(两个有向边)只访问一次, int bcc[MAXV]; //标记点属于哪个边双连通分支 int deg[MAXV]; //缩点后的新图节点度数,便于计算叶子结点数 void fill(int u){ vis[u] = 1; bcc[u] = bcc_num; //cout << u << " " << bcc[u] << endl; for (int i = head[u]; i != -1; i = arc[i].next){ if (bridge[i]) continue; int v = arc[i].v; if (!vis[v]) fill(v); } } int find_bcc(int n){ mem(vis, 0); mem(deg, 0); //确定每个点所属边双联通分量 for (int i = 1; i <= n; i ++){ if (!vis[i]){ ++ bcc_num; fill(i); } } mem(vis_arc, 0); //计算还需连几条边才能构成双联通图 for (int i = 0; i < cnt; i ++){ if (!vis_arc[i]){ //if(bridge[i]){ vis_arc[i] = vis_arc[arc[i].opp] = 1; int u = arc[i].u; int v = arc[i].v; if (bcc[u] != bcc[v]){ deg[bcc[u]] ++; deg[bcc[v]] ++; } } } int res = 0; for (int i = 1; i <= bcc_num; i ++){ if (deg[i] == 1) res ++; } return (res+1)/2; } void solve(int n){ id = bcc_num =0; mem(dfn, 0); mem(low, 0); mem(bridge, 0); for (int i = 1; i <= n; i ++){ if (!dfn[i]) tarjan(1, 0); } printf("%d\n", find_bcc(n)); return ; } int main(){ int F,R; while(scanf("%d %d", &F, &R) != EOF){ init(); for (int i = 0; i < R; i ++){ int u, v; scanf("%d %d", &u, &v); add(u, v); } solve(F); } return 0; } 
               
              
             
           
          
         
        
       
      
    
 
   
  POJ 3177 (有重边):
#include 
 
   
    
  
#include 
  
    
      #include 
     
       #include 
      
        #include 
       
         #include 
        
          #include 
         
           #include 
          
            #include 
            #include 
            
              #include 
             
               #include 
              
                #define MID(x,y) ((x+y)>>1) #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; const int MAXV = 5005; const int MAXE = 20005; struct node{ int u, v; int next; int opp; //把一个无向边拆成两个有向边,对应反向边标号 }arc[MAXE]; int cnt, head[MAXV]; void init(){ cnt = 0; mem(head, -1); return ; } void add(int u, int v){ arc[cnt].u = u; arc[cnt].v = v; arc[cnt].next = head[u]; arc[cnt].opp = cnt + 1; head[u] = cnt ++; arc[cnt].u = v; arc[cnt].v = u; arc[cnt].next = head[v]; arc[cnt].opp = cnt - 1; head[v] = cnt ++; return ; } int id, dfn[MAXV], low[MAXV]; int bridge[MAXE]; //标记该边是不是桥 bool vis_arc[MAXE]; //一条边无向边(两个有向边)只访问一次, void tarjan(int u, int father){ dfn[u] = low[u] = ++id; for (int i = head[u]; i != -1; i = arc[i].next){ int v = arc[i].v; if (vis_arc[i]) continue; vis_arc[i] = vis_arc[arc[i].opp] = 1; if (!dfn[v]){ tarjan(v, u); low[u] = min(low[u], low[v]); if (dfn[u] < low[v]){ bridge[i] = bridge[arc[i].opp] = 1; } } else{ low[u] = min(low[u], dfn[v]); } } } int bcc_num; bool vis[MAXV]; int bcc[MAXV]; //标记点属于哪个边双连通分支 int deg[MAXV]; //缩点后的新图节点度数,便于计算叶子结点数 void fill(int u){ vis[u] = 1; bcc[u] = bcc_num; //cout << u << " " << bcc[u] << endl; for (int i = head[u]; i != -1; i = arc[i].next){ if (bridge[i]) continue; int v = arc[i].v; if (!vis[v]) fill(v); } } int find_bcc(int n){ mem(vis, 0); mem(deg, 0); //确定每个点所属边双联通分量 for (int i = 1; i <= n; i ++){ if (!vis[i]){ ++ bcc_num; fill(i); } } mem(vis_arc, 0); //计算还需连几条边才能构成双联通图 for (int i = 0; i < cnt; i ++){ if (!vis_arc[i]){ vis_arc[i] = vis_arc[arc[i].opp] = 1; int u = arc[i].u; int v = arc[i].v; if (bcc[u] != bcc[v]){ deg[bcc[u]] ++; deg[bcc[v]] ++; } } } int res = 0; for (int i = 1; i <= bcc_num; i ++){ if (deg[i] == 1) res ++; } return (res+1)/2; } void solve(int n){ id = bcc_num =0; mem(dfn, 0); mem(low, 0); mem(bridge, 0); mem(vis_arc, 0); for (int i = 1; i <= n; i ++){ if (!dfn[i]) tarjan(1, 0); } printf("%d\n", find_bcc(n)); return ; } int main(){ int F,R; while(scanf("%d %d", &F, &R) != EOF){ init(); for (int i = 0; i < R; i ++){ int u, v; scanf("%d %d", &u, &v); add(u, v); } solve(F); } return 0; } 
               
              
             
           
          
         
        
       
      
    
 
   

你可能感兴趣的:(poj)