Network of Schools[USACO]
将连通分量求出后,收缩为一个节点。(这里我没有对两个连通分量间的边进行收缩为一条边)。
新图中,入度为0的点都需要注入新软件,且注入新软件后,其余连通分量都能获得新软件。故入度为0的节点个数为第一问答案。
设入度为0点个数为m,出度为0点个数为n,如果m>n。我们把入度为0与出度为0的m对节点相连,可以想象一下:存在一种方案,可以得到一个环,它把这m对节点都连通在内,剩下的m-n个入度为0的点,再添加反方向的边,他们也变为了连通。而且这些收缩操作与第一步形成的环可以确保这些连通分量间也是连通的。所以,只要添加m条边即可实现全连通。同理,如果n>m,则答案为n。 不过有一个特殊情况,如果新图只有一个节点,即原图本就全连通,则输出0.
求连通分量用的2遍dfs。
/* ID: zhangyc1 LANG: C++ TASK: schlnet */ #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> using namespace std; int N; struct SLink { int nNum; SLink* pNext; SLink():nNum(-1), pNext(NULL){} SLink(int n):nNum(n), pNext(NULL){} }; SLink arrLink[101], arrLinkTp[101];// G, G'的邻接表 SLink* pTail[101], *pTailTp[101];// 邻接表的尾指针 int arrIn[101], arrOut[101];// 入度,出度 int nCount = 0, arrClass[101];// 总连通分量个数,每个节点所属连通分量 int nTime = 0, arrTime[101], arrSort[101];// dfs顺序 bool arrVisited[101]; void dfs1(int nPos); void dfs2(int nPos); void prepairData() { scanf("%d", &N); int nId; for (int i = 1; i <= N; i++) { pTail[i] = &arrLink[i]; pTailTp[i] = &arrLinkTp[i]; } for (int i = 1; i <= N; i++) { while (scanf("%d", &nId) && nId != 0) { pTail[i]->pNext = new SLink(nId); pTail[i] = pTail[i]->pNext; pTailTp[nId]->pNext = new SLink(i); pTailTp[nId] = pTailTp[nId]->pNext; } } memset(arrIn, 0, sizeof(arrIn)); memset(arrOut, 0, sizeof(arrOut)); memset(arrVisited, 0, sizeof(arrVisited)); } void process() { for (int i = 1; i <= N; i++) if (!arrVisited[i]) dfs1(i); for (int i = 1; i <= N; i++) arrSort[nTime - arrTime[i]] = i; memset(arrVisited, 0, sizeof(arrVisited)); for (int i = 1; i <= N; i++) if (!arrVisited[arrSort[i]]) { nCount++; dfs2(arrSort[i]); } // 收缩强连通,并计算入出度 for (int i = 1; i <= N; i++) { SLink* pNeighbour = arrLink[i].pNext; while (pNeighbour) { if (arrClass[i] != arrClass[pNeighbour->nNum]) { arrOut[arrClass[i]]++; arrIn[arrClass[pNeighbour->nNum]]++; } pNeighbour = pNeighbour->pNext; } } int nZeroInNum = 0, nZeroOutNum = 0; for (int i = 1; i <= nCount; i++) { if (arrIn[i] == 0) nZeroInNum++; if (arrOut[i] == 0) nZeroOutNum++; } printf("%d\n", nZeroInNum); if (nCount == 1) printf("0\n"); else printf("%d\n", max(nZeroInNum, nZeroOutNum)); } void dfs1(int nPos) { arrVisited[nPos] = true; SLink* pNeighbour = arrLink[nPos].pNext; while (pNeighbour) { if (!arrVisited[pNeighbour->nNum]) dfs1(pNeighbour->nNum); pNeighbour = pNeighbour->pNext; } arrTime[nPos] = nTime++; } void dfs2(int nPos) { arrVisited[nPos] = true; arrClass[nPos] = nCount; SLink* pNeighbour = arrLinkTp[nPos].pNext; while(pNeighbour) { if (!arrVisited[pNeighbour->nNum]) dfs2(pNeighbour->nNum); pNeighbour = pNeighbour->pNext; } } int main(){ FILE *streamIn = freopen("schlnet.in","r",stdin); FILE *streamOut = freopen("schlnet.out","w",stdout); prepairData(); process(); fclose(streamIn); fclose(streamOut); return 0; }
还有一个tarjan算法,也学习了一下
/* ID: zhangyc1 LANG: C++ TASK: schlnet */ #include <string> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> using namespace std; int N; struct SLink { int nNum; SLink* pNext; SLink():nNum(-1), pNext(NULL){} SLink(int n):nNum(n), pNext(NULL){} }; SLink arrLink[101];// G的邻接表 SLink* pTail[101];// 邻接表的尾指针 int arrIn[101], arrOut[101];// 入度,出度 int nCount = 0, arrClass[101];// 总连通分量个数,每个节点所属连通分量 int arrDFN[101], arrLow[101], nTime = 0;// bool arrVisited[101], arrProcessed[101]; int arrSt[101], nCurTop = 0; void tarjan(int nPos); void prepairData() { scanf("%d", &N); int nId; for (int i = 1; i <= N; i++) { pTail[i] = &arrLink[i]; } for (int i = 1; i <= N; i++) { while (scanf("%d", &nId) && nId != 0) { pTail[i]->pNext = new SLink(nId); pTail[i] = pTail[i]->pNext; } } memset(arrIn, 0, sizeof(arrIn)); memset(arrOut, 0, sizeof(arrOut)); memset(arrVisited, 0, sizeof(arrVisited)); memset(arrProcessed, 0, sizeof arrProcessed); } void process() { for (int i = 1; i <= N; i++) if (!arrVisited[i]) tarjan(i); // 收缩强连通,并计算入出度 for (int i = 1; i <= N; i++) { SLink* pNeighbour = arrLink[i].pNext; while (pNeighbour) { if (arrClass[i] != arrClass[pNeighbour->nNum]) { arrOut[arrClass[i]]++; arrIn[arrClass[pNeighbour->nNum]]++; } pNeighbour = pNeighbour->pNext; } } int nZeroInNum = 0, nZeroOutNum = 0; for (int i = 1; i <= nCount; i++) { if (arrIn[i] == 0) nZeroInNum++; if (arrOut[i] == 0) nZeroOutNum++; } printf("%d\n", nZeroInNum); if (nCount == 1) printf("0\n"); else printf("%d\n", max(nZeroInNum, nZeroOutNum)); } void tarjan(int nPos) { arrDFN[nPos] = nTime; arrLow[nPos] = nTime; arrVisited[nPos] = true; arrSt[nCurTop++] = nPos; nTime++; SLink* pLink = arrLink[nPos].pNext; while (pLink) { // 未访问过则访问子节点,并判断是否更新low值 if (!arrVisited[pLink->nNum]) { tarjan(pLink->nNum); if (arrLow[nPos] > arrLow[pLink->nNum]) arrLow[nPos] = arrLow[pLink->nNum]; } // 子节点已访问过,且子节点仍在栈中,说明其子节点在更低的层。判断是否可用其子节点的时间值去更新当前的low值 else if (!arrProcessed[pLink->nNum] && arrLow[nPos] > arrDFN[pLink->nNum]) arrLow[nPos] = arrDFN[pLink->nNum]; pLink = pLink->pNext; } // 当前节点为连通分量的树根,输出 if (arrDFN[nPos] == arrLow[nPos]) { nCount++; do { nCurTop--; arrClass[arrSt[nCurTop]] = nCount; arrProcessed[arrSt[nCurTop]] = true; } while (arrSt[nCurTop] != nPos); } } int main(){ FILE *streamIn = freopen("schlnet.in","r",stdin); FILE *streamOut = freopen("schlnet.out","w",stdout); prepairData(); process(); fclose(streamIn); fclose(streamOut); return 0; }