P8867 [NOIP2022] 建造军营
缩点
首先考虑对于一个边双内的边是可以任意看守或者不看守的,所以可以缩点,这样缩完点的图就成了一棵树。
对于缩完点后的每一个边双,我们设 \(E_{i}\) 表示边双 \(i\) 内的边的个数,\(siz_{i}\) 表示边双内点的个数。
那么只考虑一个边双的情况的话,边能任选的方案数就是 \(2^{E_i}\),剩余的就是在此边双内建造军营的方案数,也就是 \(2^{E_i + siz_i} - 2^{E_i}\)。
状态
设 \(f_{u,1/0}\) 表示以 \(u\) 为根的子树内建造/不建造军营的方案数。
但是涵盖情况太多,考虑增加限制。
设 \(f_{u, 1/0}\) 表示以 \(u\) 为根的子树内有/无军营的方案数,若有军营,则军营到 \(u\) 的路径上的边都被看守。
同时强制除 \(u\) 子树外的点都不建造军营,且 \(u\to fa_{u}\) 的边强制不看守。
转移
令 \(s_u\) 为子树 \(u\) 内边的个数,则有 \(ans\gets f_{u, 1} \times 2^{s_1 - s_u - 1}\)。
因为上面状态里说了强制除了子树内有军营以外,其他点都没有军营,所以是用 \(f_{u,1}\) 去乘其他剩余边的自由选的方案数,其中边数后面减一是 \(u\to fa_u\) 的强制不选,所以不算入。
对于 \(1\) 号节点,很特殊,往上就没有多余的边了,直接就是 \(f_{1,1}\)。
然后考虑如何转移。
对于 \(f_{u, 0}\) 就是 \(f_{u, 0} = f_{u, 0} \prod_{v\in son(u)} 2\times f_{v, 0}\),这里乘 \(2\) 是因为 \(v\to u\) 的边不在 \(f_{v, 0}\) 内计算,需要单独算。
对于 \(f_{u, 1}\),如果当前 \(u\) 内子树还没有军营,那么 \(v\) 内子树一定有军营,反之 \(v\) 子树内有无军营皆可,也就是:
code
/*
* @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;
}
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/articles/17791846.html
The heart is higher than the sky, and life is thinner than paper.