博客 > 数据结构&算法 > 题目&比赛
# HDU-1827 Tarjan简单应用 ## [HDU-1827](http://acm.hdu.edu.cn/showproblem.php?pid=1827) ## 分析 可以分为 **最小联系人数** + **最小联系花费** 两部分 ### 最小联系人数 首先使用Tarjan求强连通分量, 每个分量中只需要通知一个人. 在这个基础上深入思考: 其实任意的一个缩点都可以通知它连接到的其他缩点, 也就是说, **凡是入度不为零的缩点, 都不必通知**. 同时又已知: 一个图中必然有至少一个孤立的强连通分量(缩点入度为0). 因此题目不会无解. 最终结论是**求入度为0的缩点数** ### 最小联系花费 给每个缩点入度为0的强连通分量配一个联系人就完事了. 当然是每个都**枚举出花费最小的**. ## 心得 刚开始觉得还需要用DP或者别的来求最优解, 因为不知道怎么保证最小联系人数下一定有最小联系花费, 万一多联系几个人反而花费得更少呢? 后来发现是本宝宝多虑了: 1. 每一个缩点入度为0的强连通分量都必定需要一个联系人, 不然谁也联系不上里面的人. **从这些分量里各出一个人是必须的**. 2. 在这个基础上再加任何一个人, 都是没有必要的——既不能减少花费(多联系人肯定多花), 也不能联系到更多人(没人需要联系了). 所以**只给孤立分量分配联系人, 就是人数和花费的双最优解**. ## 代码 ```cpp // -------------- GLOBAL ---------------- vector<int> map1[1050]; // 邻接表, 统一从0开始 stack<int> stk1; // 栈 int n, m; int dfn[1050], low[1050], book[1050]; // dfs序, 最低dfs序, 强连通分量 int dfsClock = 0; // 真实dfs树的遍历顺序 int cnt = 0; // 分量数的累加变量, 注意是从1开始的 int in[1050]; // 入度(注意这里只能判断为0, 多了不靠谱) int cost[1050]; // 花费 int res[2]; // 结果(最小人数和最小总花费) // -------------- FUNC ---------------- void tarjan(int k) { dfn[k] = low[k] = ++dfsClock; stk1.push(k); FE(v, map1[k]) { if (!dfn[v]) { tarjan(v); low[k] = min(low[k], low[v]); } else if (!book[v]) { low[k] = min(low[k], dfn[v]); } } // 到根了 if (dfn[k] == low[k]) { ++cnt; while (1) { int top = stk1.top(); book[top] = cnt; stk1.pop(); if (top == k) break; } } } // -------------- MAIN ---------------- int main() { while (~scanf("%d%d", &n, &m)) { dfsClock = cnt = 0; fill0(dfn); fill0(book); fill0(in); fill0(res); F0(i, n) { map1[i].clear(); } // 输入 F0(i, n) { scanf("%d", &cost[i]); } F0(i, m) { int u, v; scanf("%d%d", &u, &v); map1[--u].push_back(--v); } // Tarjan F0(i, n) { if (!dfn[i]) { tarjan(i); } } // 找入度为0的强连通分量 F0(u, n) { FE(v, map1[u]) { if (book[u] != book[v]) { ++in[book[v]]; } } } // 找入度为0的强连通分量中花费最小的联系人 F1(i, cnt) { if (!in[i]) { int min1 = INF; F0(j, n) { if (book[j] == i) { min1 = min(min1, cost[j]); } } ++res[0]; // 联系人个数 res[1] += min1; // 联系人总花费 } } printf("%d %d\n", res[0], res[1]); } return 0; } ```