P8867 NOIP2022 建造军营
P8867 NOIP2022 建造军营 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)。
给定一个无向联通图
, , 。- 在
上,断开 中任意一条边后,都有 中所有点在 上仍然联通。
35 pts
枚举
时间复杂度
45 pts
考虑开特殊性质
我们考虑当
当
当
到这里都很送。
100 pts
不难发现,对于任意非桥边,删除它后整个图都可联通,因此所有非桥边在任意情况下都可以随便选。
这启发我们对整个图进行边双缩点。边双缩点后形成一棵树,原图非桥边将全部消失(被缩在一个点里),桥边变成新的树边。
因为任何非桥边任意情况下可以任意选,所以最终答案会有一个
当模型变成树后,我们发现,任意两个点之间的路径变得唯一:也就是对于树上的任意两个节点
为了区分,我们定义
计数问题要么是动态规划要么是排列组合,要么两个都占。接下来就不难想到树形 dp 了。我们记
考虑设计状态,
对于
若我们在
如果
如果
上面两种情况会造成转移的截然不同,但是由于我们统统放在一个
换句话说,我们此时应给状态多加一维:
从而分开转移。最终答案是
详细的转移方程和代码细节可以参考别的题解,因为在这里我要讲的是一种另外的思路,状态不需要多加一维,更有趣一点。
设
我们加设
接着讨论
上不选点:那么 上的边和 这条边都有选和不选两种可能,有 种情况; 上选点:此时 必选,有 种情况。
最后还有个问题:
得到
怎么做答案统计?
答案一定不是
我们再考虑,一种选择方案,它选的所有点全都在
此时我想,答案是否为
上面这个选择方案(红点和红边为选择对象,黑色的未不选的对象),会被
上面这个选择方案,会被
是否有一种办法能让每种方案只被统计一次?
观察上面这两个反例和自己造的一些反例,不难发现,只在所选点集的 LCA 统计答案即可。也就是对于第一个反例,期望在
换句话讲,我们的目标:对于节点
再换句话讲,就是计算出在
再再换句话讲,就是计算出在
我们枚举
因此,
时间复杂度
/*
* @Author: crab-in-the-northeast
* @Date: 2022-12-07 04:15:55
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-12-07 05:23:10
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
inline bool gmi(int &a, int b) {
return b < a ? a = b, true : false;
}
const int maxn = (int)5e5 + 5;
const int maxm = (int)1e6 + 5;
std :: vector <int> G[maxn], T[maxn];
int dfn[maxn], low[maxn], snt = 0, times = 0, sno[maxn];
int c[maxn];
std :: stack <int> s;
void tarjan(int u, int fa) {
low[u] = dfn[u] = ++times;
s.push(u);
for (int v : G[u]) {
if (!dfn[v]) {
tarjan(v, u);
gmi(low[u], low[v]);
} else if (v != fa)
gmi(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++snt;
for (; ;) {
int x = s.top();
s.pop();
sno[x] = snt;
++c[snt];
if (x == u)
break;
}
}
}
const int mod = (int)1e9 + 7;
int f[maxn], ans = 0;
int p[maxm], e[maxn];
void dp(int u, int fa) {
f[u] = p[c[u]];
for (int v : T[u]) {
if (v == fa)
continue;
dp(v, u);
(f[u] *= (p[e[v] + 1] + f[v])) %= mod;
e[u] += e[v] + 1;
}
(f[u] += mod - p[e[u]]) %= mod;
int now = f[u];
for (int v : T[u]) {
if (v == fa)
continue;
((now -= f[v] * p[e[u] - e[v] - 1] % mod) += mod) %= mod;
}
(ans += now * p[snt - 1 - e[u]] % mod) %= mod;
}
signed main() {
int n = read(), m = read();
for (int _ = 1; _ <= m; ++_) {
int u = read(), v = read();
G[u].push_back(v);
G[v].push_back(u);
}
tarjan(1, 0);
for (int u = 1; u <= n; ++u)
for (int v : G[u])
if (sno[u] != sno[v])
T[sno[u]].push_back(sno[v]);
p[0] = 1;
for (int i = 1; i <= m + 3; ++i)
p[i] = (p[i - 1] << 1) % mod;
dp(1, 0);
printf("%lld\n", ans * p[m - e[1]] % mod);
return 0;
}
如果觉得这篇题解写得好,请不要忘记点赞,谢谢!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探