「NOIP2022」建造军营 题解
前言
题目链接:洛谷。
题意简述
yzh 送你一张
题目分析
这么大一张无向图,要么考虑 tarjan 缩点,要么考虑随便建出一棵 DFS 树,分为树边、非树边考虑。对于此题,两种做法均可,作者仅介绍更为好像的 tarjan 缩点做法,DFS 树方法留给读者思考。
发现,yzh 选择的边会导致原图不连通,当且仅当选择了图上的桥。用 tarjan 把边双缩点后,图变成了一棵树。问题此时转变成了,在
形式化地表示如下:
这样一坨东西应该需要树形 DP 解决。
从
那么 DP 的状态也很容易想了,用
如何统计答案呢?我们考虑在联通块最浅的那个点统计。
此时树形 DP 时间复杂度已经是
我们发现,完全不需要记
这个简单容斥以下等价于下者:
于是又可以解决问题了。我们进一步发现,在转移的时候,自始至终都在使用
代码
轻松最优解。
#include <cstdio> #include <iostream> using namespace std; const int mod = 1e9 + 7, inv2 = (mod + 1) >> 1; inline int add(int a, int b) { return a >= mod - b ? a + b - mod : a + b; } inline int sub(int a, int b) { return a < b ? a - b + mod : a - b; } inline int mul(int a, int b) { return 1ll * a * b % mod; } const int N = 500010, M = 1000010; struct Graph { struct node { int to, nxt; } edge[M << 1]; int head[N], tot = 1; inline void add(int u, int v) { edge[++tot] = { v, head[u] }; head[u] = tot; } inline node & operator [] (int x) { return edge[x]; } } xym, yzh; int n, m; int dfn[N], low[N], timer; int stack[N], top; int sccno[N], scc_cnt, siz[N]; void tarjan(int u, int fr) { dfn[u] = low[u] = ++timer, stack[++top] = u; for (int i = xym.head[u]; i; i = xym[i].nxt) { if ((i ^ 1) == fr) continue; int v = xym[i].to; if (!dfn[v]) tarjan(v, i), low[u] = min(low[u], low[v]); else low[u] = min(low[u], dfn[v]); } if (low[u] >= dfn[u]) { ++scc_cnt; do { int v = stack[top--]; sccno[v] = scc_cnt; ++siz[scc_cnt]; } while (stack[top + 1] != u); } } int pw[M], f[N], ans; void dfs(int u, int fa) { int one = 1; for (int i = yzh.head[u]; i; i = yzh[i].nxt) { int v = yzh[i].to; if (v == fa) continue; dfs(v, u); ans = sub(ans, mul(inv2, f[v])); one = mul(one, add(mul(inv2, f[v]), 1)); } f[u] = mul(sub(one, 1), pw[siz[u]]); f[u] = add(f[u], sub(pw[siz[u]], 1)); ans = add(ans, f[u]); } signed main() { #ifndef XuYueming freopen("barrack.in", "r", stdin); freopen("barrack.out", "w", stdout); #endif scanf("%d%d", &n, &m); for (int i = 1, u, v; i <= m; ++i) { scanf("%d%d", &u, &v); xym.add(u, v), xym.add(v, u); } tarjan(1, 0); for (int u = 1; u <= n; ++u) for (int i = xym.head[u]; i; i = xym[i].nxt) { int v = xym[i].to; if (sccno[u] == sccno[v]) continue; yzh.add(sccno[u], sccno[v]); } pw[0] = 1; for (int i = 1; i <= max(n, m); ++i) pw[i] = add(pw[i - 1], pw[i - 1]); dfs(1, 0), ans = mul(ans, pw[m]); printf("%d", ans); return 0; }
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18523756。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)