首先考虑这个 \(S, T\) 肯定需要固定一个算另一个的方案数。
如果固定 \(S\),会发现非常不好给 \(T\) 下限制。
于是考虑固定 \(T\),对 \(S\) 计数。
首先考虑如果 \(T\) 只有 \(2\) 个点 \(x, y\),该怎么对 \(S\) 计数。
考虑到这个简单路径的定义是不经过重点,考虑找到点双。
然后能发现,只要是在 \(x\to y\) 这个路径上经过的点双里的点,都是可以放入 \(S\) 的。
于是这启发找到点双后建出圆方树并在上面 DP。
首先要考虑圆点方点的权值 \(a_u\)。
根据前面说到的,可以知道经过的方点的对应的点集的并就是可以放入 \(S\) 的点集。
令 \(\operatorname{siz}_u\) 为方点 \(u\) 对应的点的个数,令 \(U\) 为经过的方点的集合。
考虑到对于由圆点连接的两个方点都会算上这个原点,应该 \(-1\)。
所以能够知道 \(|S| = (\sum\limits_{u\in U} \operatorname{siz}_u) - (|U| - 1) = (\sum\limits_{u\in U}(\operatorname{siz}_u - 1)) + 1\)。
那么这样就只与 \(u\) 有关而不用考虑 \(|U|\) 之类的东西了。
于是可以把方点的权值设为 \(a_u = \operatorname{siz}_u - 1\), 圆点设置为 \(a_u = 0\)。
对于少考虑的 \(1\) 最后补上就行了。
同时需要考虑到知道了 \(T\),那么哪些方点 \(u\) 是能被经过的。
这个易知只要 \(u\) 在 \(T\) 对应在圆方树上的虚树中,\(u\) 就可以经过。
同时如果知道了 \(T\) 对应的 \(S\),其对应的权值显然为 \(2^{|S|}\)。
于是就可以考虑 DP 了。
先定义 \(\operatorname{subtree}_u\) 表示 \(u\) 的子树,\(\operatorname{son}_u\) 为 \(u\) 的儿子,\(\operatorname{val}(u \to v)\) 表示 \(u\to v\) 这条路径上经过的点 \(w(w\not = v)\) 的 \(\prod 2^{a_w}\),也就是这条路径上的方点产生的贡献。
考虑从上面说到的虚树来考虑,令 \(f_u\) 为 \(u\) 为虚树的根是对应的方案数。
但会发现这样根本不好转移,因为虚树的根可能是因为两个不同子树里的根在此处合并导致的。
于是考虑在令 \(g_u\) 为 \(u\) 子树内任意一个点为根对应的方案数,具体表示一下就是 \(g_u = \sum\limits_{v\in \operatorname{subtree}(u)} f_v\times \operatorname{val}(u\to v)\)。
然后考虑转移,分为圆点和方点:
- 对于圆点为根,有 \(2\) 种选择:自己本身就被选中或者有两个不同子树有点被选中。
所以说如果有 \(\ge 2\) 个不同子树内有被选中的,\(2\) 种方法都可以;但若是只有 \(1\) 个子树或者没有,就只可能是自己被选中 \(1\) 种。
可以对 \(g_v(v\in \operatorname{son}_u)\) 做个背包得到 \(h_{0\sim 2}\),其中 \(h_x(0\le x\le 1)\) 分别代表有 \(x\) 个不同子树内有点被选中的方案数,\(h_2\) 代表有 \(\ge 2\) 个不同子树内有点被选中的方案数。
那么有 \(f_u = 2h_2 + h_1 + h_0\)。 - 对于方点为根,那么就只能是有两个不同子树有点被选中了。
同上一样对 \(g_v(v\in \operatorname{son}_u)\) 做背包得到 \(h_{0\sim 2}\)。
那么因为 \(u\) 是方点,有对应的贡献 \(2^{a_u}\),所以有 \(f_u = h_2\times 2^{a_u}\)。
然后对于 \(g_u\),考虑有 \(\operatorname{val}(u\to w) = 2^{a_u}\operatorname{val}(v\to w)(w\in \operatorname{subtree}(v), v\in \operatorname{son}_u)\),于是只需所有 \(g_v\) 乘上 \(2^{a_u}\) 后,再加上新出现的 \(f_u\) 就行了。
即 \(g_u = h_1 2^{a_u} + f_u\)。
最后记得补上之前少的 \(1\),乘 \(2\) 即可,并补上 \(S = T = \varnothing\) 的 \(1\)。
时间复杂度 \(\mathcal{O}(n)\)。
#include<bits/stdc++.h>
using ll = long long;
constexpr ll mod = 998244353;
const int maxn = 1e6 + 10;
int n, N;
ll pw[maxn];
std::vector<int> G[maxn], to[maxn];
int low[maxn], dfn[maxn], dt, stk[maxn], top;
int siz[maxn];
void dfs1(int u) {
low[u] = dfn[u] = ++dt, stk[++top] = u;
for (int v : G[u]) {
if (dfn[v]) low[u] = std::min(low[u], dfn[v]);
else {
dfs1(v), low[u] = std::min(low[u], low[v]);
if (dfn[u] == low[v]) {
N++, to[u].push_back(n + N), to[n + N].push_back(u);
int t;
do {t = stk[top--]; to[t].push_back(n + N), to[n + N].push_back(t);} while (t != v);
siz[n + N] = to[n + N].size();
}
}
}
}
ll f[maxn], g[maxn];
void dfs2(int u, int fa) {
if (u != 1 && to[u].size() == 1)
return f[u] = g[u] = 1, void();
ll h[3] = {1, 0, 0};
for (int v : to[u]) {
if (v == fa) continue;
dfs2(v, u);
h[2] = (h[2] * (g[v] + 1) + h[1] * g[v]) % mod, h[1] = (h[1] + h[0] * g[v]) % mod;
}
if (u > n) f[u] = pw[siz[u] - 1] * h[2] % mod, g[u] = (pw[siz[u] - 1] * h[1] + f[u]) % mod;
else f[u] = (2ll * h[2] + h[1] + h[0]) % mod, g[u] = (h[1] + f[u]) % mod;
}
int main() {
int m; scanf("%d%d", &n, &m);
for (int i = pw[0] = 1; i <= n; i++) (pw[i] = pw[i - 1] << 1) >= mod && (pw[i] -= mod);
for (int x, y; m--; )
scanf("%d%d", &x, &y), G[x].push_back(y), G[y].push_back(x);
dfs1(1);
dfs2(1, 0);
ll ans = 0;
for (int i = 1; i <= n + N; i++) (ans += f[i]) %= mod;
printf("%lld\n", (2ll * ans + 1ll) % mod);
return 0;
}
标签:int,Luogu,S1,T3,方点,maxn,low,operatorname,mod
From: https://www.cnblogs.com/rizynvu/p/18281036