あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

P8867 [NOIP2022] 建造军营

P8867 [NOIP2022] 建造军营

缩点#

首先考虑对于一个边双内的边是可以任意看守或者不看守的,所以可以缩点,这样缩完点的图就成了一棵树。

对于缩完点后的每一个边双,我们设 Ei 表示边双 i 内的边的个数,sizi 表示边双内点的个数。

那么只考虑一个边双的情况的话,边能任选的方案数就是 2Ei,剩余的就是在此边双内建造军营的方案数,也就是 2Ei+sizi2Ei

状态#

fu,1/0 表示以 u 为根的子树内建造/不建造军营的方案数。

但是涵盖情况太多,考虑增加限制。

fu,1/0 表示以 u 为根的子树内有/无军营的方案数,若有军营,则军营到 u 的路径上的边都被看守。

同时强制除 u 子树外的点都不建造军营,且 ufau 的边强制不看守。

转移#

su 为子树 u 内边的个数,则有 ansfu,1×2s1su1

因为上面状态里说了强制除了子树内有军营以外,其他点都没有军营,所以是用 fu,1 去乘其他剩余边的自由选的方案数,其中边数后面减一是 ufau 的强制不选,所以不算入。

对于 1 号节点,很特殊,往上就没有多余的边了,直接就是 f1,1

然后考虑如何转移。

对于 fu,0 就是 fu,0=fu,0vson(u)2×fv,0,这里乘 2 是因为 vu 的边不在 fv,0 内计算,需要单独算。

对于 fu,1,如果当前 u 内子树还没有军营,那么 v 内子树一定有军营,反之 v 子树内有无军营皆可,也就是:

fu,1=fu,0×fv,1+fu,1×(2×fv,0+fv,1)

code#

Copy
/* * @Author: Aisaka_Taiga * @Date: 2023-10-27 9:06:57 * @LastEditTime: 2023-10-27 11:14:52 * @LastEditors: Aisaka_Taiga * @FilePath: \Desktop\P8867.cpp * The heart is higher than the sky, and life is thinner than paper. */ #include <bits/stdc++.h> #define int long long #define P 1000000007 #define N 1000100 using namespace std; inline int read() { int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();} while(c <= '9' && c >= '0') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return x * f; } int n, m, head[N << 1], cnt, E[N], sd[N], dfn[N], vis[N], stk[N], top, low[N], tim, scc, siz[N], ans, s[N], f[N][2]; struct node{int v, next;}e[N << 1]; vector<int> g[N]; inline void add(int u, int v){e[++ cnt] = (node){v, head[u]}; head[u] = cnt;} inline void tarjan(int u, int fa) { dfn[u] = low[u] = ++ tim; stk[++ top] = u; vis[u] = 1; for(int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(v == fa) continue; if(!dfn[v]) tarjan(v, u), low[u] = min(low[u], low[v]); else if(vis[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]) { int y; scc ++; while(1) { y = stk[top --]; vis[y] = 0; sd[y] = scc; siz[scc] ++; if(u == y) break; } } return ; } inline int ksm(int u, int v) { int res = 1; while(v) { if(v & 1) res = (res * u) % P; u = (u * u) % P, v >>= 1; } return res; } inline void dfs(int u, int fa) { s[u] = E[u]; for(auto v : g[u]) { if(v == fa) continue; dfs(v, u); s[u] += s[v] + 1; } return ; } inline void DP(int u, int fa) { for(auto v : g[u]) { if(v == fa) continue; DP(v, u); f[u][1] = ((f[u][1] * (f[v][0] * 2 % P + f[v][1]) % P) % P + (f[u][0] * f[v][1]) % P) % P; f[u][0] = f[u][0] * (f[v][0] * 2 % P) % P; } if(u == 1) ans = (ans + f[u][1]) % P; else ans = (ans + f[u][1] * ksm(2, s[1] - s[u] - 1)) % P; return ; } signed main() { n = read(), m = read(); for(int i = 1; i <= m; i ++) { int u = read(), v = read(); add(u, v), add(v, u); } tarjan(1, 0); for(int u = 1; u <= n; u ++) { for(int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(sd[u] == sd[v]) E[sd[u]] ++; else g[sd[u]].emplace_back(sd[v]); } } for(int i = 1; i <= scc; i ++) { E[i] >>= 1; f[i][0] = ksm(2, E[i]); f[i][1] = ksm(2, siz[i] + E[i]) - f[i][0]; } dfs(1, 0); DP(1, 0); // for(int i = 1; i <= n; i ++) // cout << sd[i] << endl; cout << ans << endl; return 0; }
posted @   北烛青澜  阅读(39)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
目录