luogu3225 [HNOI2012]矿场搭建
题目大意
给出一个有$n(n\leq 500)$个节点的无向图,一个满足条件的点集$V$会使得对于图中的每一个节点$u$,满足路径起点为$u$终点$v\in V$的路径集合$P_u$中总存在至少两条路径$p_1,p_2$,使得该两条路径除了起点外没有交集(终点也不同)。输出$|V|$的最小值,以及$|V|$最小时$V$的种类数。
题解
对于一个点双连通分量中的任意一对点都有两条路径到达对方,所以我们从点双连通分量入手。
特殊情况:当一个点双连通分量中没有割点时,根据题目要求,这个点双连通分量中需要有两个点属于$V$。
若把所有点双连通分量缩点形成一棵树,那么树必定会有叶子节点。所以我们考虑当一个点双连通分量有一个割点时该怎么办。考虑到去掉的点是割点的情况,每个叶子双联通分量内必需有一个点$t$属于$V$;若去掉的点位于所在连通分量以外的部分,双连通分量内的点都与$t$连通;若去掉的点在双连通分量以内且不属于割点,那么双连通分量内的其它点到割点必然存在一条路径,而割点必然与其它叶子双连通分量相连通,那里有属于$V$的点。因此,所有叶子双连通分量内必须有且只有一个点属于$V$。
若一个点双连通分量不是叶子,那么无论去掉哪个点,这个点双连通分量总与叶子连通,那里有属于$V$的点,所以这里的点双连通分量没有属于$V$的点。
关于根节点的特判,将根节点所在的点双连通分量记录下来,在Dfs外面对根节点进行特殊处理即可。
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <stack> #include <cassert> using namespace std; const int MAX_NODE = 510; struct Node { vector<Node*> Next; int DfsN, Low; bool IsCut; }_nodes[MAX_NODE]; stack<Node*> St; struct Block { int NodeCnt, CutCnt; }_blocks[MAX_NODE]; int BlockCnt; int DfsCnt; vector<Block*> RootIn; void DeStack(Node *end, Node *add) { BlockCnt++; Node *temp; do { temp = St.top(); St.pop(); _blocks[BlockCnt].NodeCnt++; _blocks[BlockCnt].CutCnt += temp->IsCut; } while (temp != end); _blocks[BlockCnt].NodeCnt++; _blocks[BlockCnt].CutCnt += add->IsCut; } int Dfs(Node *cur) { cur->Low = cur->DfsN = ++DfsCnt; St.push(cur); int cnt = 0; for (int i = 0; i < cur->Next.size(); i++) { if (!cur->Next[i]->DfsN) { Dfs(cur->Next[i]); cur->Low = min(cur->Low, cur->Next[i]->Low); if (cur->Next[i]->Low >= cur->DfsN) { cnt++; if (cur != _nodes + 1) cur->IsCut = true; DeStack(cur->Next[i], cur); if (cur == _nodes + 1) RootIn.push_back(_blocks + BlockCnt); } } else cur->Low = min(cur->Low, cur->Next[i]->DfsN); } return cnt; } void Clear() { for (int i = 1; i < MAX_NODE; i++) { _nodes[i].Low = _nodes[i].DfsN = _nodes[i].IsCut = 0; _nodes[i].Next.clear(); _blocks[i].CutCnt = _blocks[i].NodeCnt = 0; } RootIn.clear(); BlockCnt = 0; DfsCnt = 0; } int main() { int totEdge, caseCnt = 0; while (scanf("%d", &totEdge) && totEdge) { Clear(); for (int i = 1; i <= totEdge; i++) { int u, v; scanf("%d%d", &u, &v); _nodes[u].Next.push_back(_nodes + v); _nodes[v].Next.push_back(_nodes + u); } int rootBlockCnt = Dfs(_nodes + 1); if (rootBlockCnt > 1) { _nodes[1].IsCut = true; for (int i = 0; i < RootIn.size(); i++) RootIn[i]->CutCnt++; } while (!St.empty()) St.pop(); int exitCnt = 0; long long solCnt = 1; for (int i = 1; i <= BlockCnt; i++) { if (_blocks[i].CutCnt == 0) { assert(BlockCnt == 1); exitCnt = 2; solCnt = (long long)_blocks[i].NodeCnt * (_blocks[i].NodeCnt - 1) / 2; } else if (_blocks[i].CutCnt == 1) { exitCnt++; solCnt *= (_blocks[i].NodeCnt - _blocks[i].CutCnt); } } printf("Case %d: %d %lld\n", ++caseCnt, exitCnt, solCnt); } return 0; }