• poj3352Road Construction 边双连通+伪缩点


    /*
    对于边双连通分支,求法更为简单。

    仅仅需在求出全部的桥以后,把桥边删除。 原图变成了多个连通块,则每一个连通块就是一个边双连通分支。

    桥不属于不论什么 一个边双连通分支,其余的边和每一个顶点都属于且仅仅属于一个边双连通分支。 一个有桥的连通图,怎样把它通过加边变成边双连通图?方法为首先求出全部的桥, 然后删除这些桥边,剩下的每一个连通块都是一个双连通子图。把每一个双连通子图收缩为一个顶点, 再把桥边加回来,最后的这个图一定是一棵树。边连通度为1。 统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。

    则至少在树上加入(leaf+1)/2条边。 就能使树达到边二连通。所以至少加入的边数就是(leaf+1)/2。详细方法为,首先把两个近期公共祖先最远 的两个叶节点之间连接一条边,这样能够把这两个点到祖先的路径上全部点收缩到一起。 由于一个形成的环一定是双连通的。然后再找两个近期公共祖先最远的两个叶节点,这样一对一对找完, 恰好是(leaf+1)/2次。把全部点收缩到了一起。 */ /* (1) n 为顶点数, 标号从 1 開始 (2) c 为原图的邻接表, g 为 E_BCC 图的邻接表 (3) num[u] 表示原图中的点 u 属于新图中的第 num[u] 个 E_BCC (4) edge[] 存储全部的桥 (5) 注意 pool[M] 要开得足够大以容得下新旧两个图中全部的边 (6) E_BCC 图中去掉了自环 ( 显然不存在多重边 ) */ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int N = 11115; const int M = 2000005; struct List { int v, id; List *next; } pool[M], *c[N], *g[N], *pp; //c 为原图的邻接表, g 为 E_BCC 图的邻接表 //注意 pool[M] 要开得足够大以容得下新旧两个图中全部的边 inline void add_edge(int u, int v, int id, List *c[]) { pp->v = v; pp->id = id; pp->next = c[u]; c[u] = pp ++; } struct Edge { int u, v; } edge[M]; //edge[] 存储全部的桥,u,v为桥的两个顶点 int n, m, label, tot, top; int low[N], dfn[N], num[N], stack[N]; bool eflag[M]; //label时间戳,tot连通块数 //dfn用来保存时间戳(次序)编号,low保存顶点i或i的子树最早的次序编号 //num[u] 表示原图中的点 u 属于新图中的第 num[u] 个 E_BCC void E_BCC_VISIT(int u) { low[u] = dfn[u] = label ++; stack[++ top] = u; for(List *p = c[u]; p; p = p->next) { int v = p->v; if(eflag[p->id]) continue; eflag[p->id] = true; //if(dfn[v]) { low[u] <?= dfn[v]; continue; } if(dfn[v]){ if(low[u] > dfn[v]) low[u] = dfn[v]; continue; } E_BCC_VISIT(v); //low[u] <?= low[v]; if(low[u] > low[v]) low[u]=low[v]; if(low[v] > dfn[u]) { edge[m].u = u;//第m条桥的两个顶点u,v edge[m ++].v = v; ++ tot; do { num[stack[top]] = tot; } while( stack[top --] != v ); } } } void E_BCC() { int i; tot = 0; m = 0;///// for(i = 1; i <= n; ++ i) dfn[i] = 0, num[i] = -1; for(i = 0; i < m; ++ i) eflag[i] = false; for(i = 1; i <= n; ++ i) if(dfn[i] == 0) { label = 1; top = -1; E_BCC_VISIT(i); ++ tot; while( top >= 0 ) { num[stack[top]] = tot; -- top; } } for(i = 1; i <= tot; ++ i) g[i] = NULL; //for(i = 1; i <= n; ++ i) {//缩点,这题用不着 // int u = num[i];//u为一个双连通分量 //for(List *p = c[i]; p; p = p->next) { // int v = num[p->v];//v是还有一个双连通分量 //if(u != v) add_edge(u, v, 0, g);//在两个分量间建一条边 //} //} } int main() { int i, j, k; while( scanf("%d %d", &n, &m) == 2 ) { for(i = 1; i <= n; ++ i) c[i] = NULL; pp = pool; for(k = 0; k < m; ++ k) { scanf("%d %d", &i, &j); add_edge(i, j, k, c); add_edge(j, i, k, c); } E_BCC(); if(m == 0){cout<<0<<endl; continue;} int du[N]={0}; for(int i=0;i<m;i++){//桥即为联通块的之间的边,这里处理伪缩点 //cout<<num[edge[i].u]<<' '<<num[edge[i].v]<<endl; du[num[edge[i].u]]++;//要用num[]映射到连通块编号上计算联通块的度 du[num[edge[i].v]]++; } int leaf=0;//树叶 //cout<<tot<<endl<<m<<endl; for(int i=1;i<=tot;i++) if(du[i]==1)leaf++; cout<<(leaf+1)/2<<endl; //for(int i=0;i<=m;i++)printf("num[%d]:%d ",i,num[i]); //printf("tot:%d m:%d ",tot,m); } return 0; }


  • 相关阅读:
    命令行颜色换算器(基于python)
    VPS常用操作(自用)
    自动读取虚拟币ETC行情并语音提醒的小工具(mac OSX)
    nginx最基本操作
    一个平庸程序员的自白
    unity 2d游戏 按y坐标排序子对象
    开源输入法推荐
    unity插件,从一段文字中提取中文并去重
    考试总结(CE???)
    螺旋矩阵
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/6912470.html
Copyright © 2020-2023  润新知