Luogu P10674 【MX-S1-T3】電動力學

rizynvu發表於2024-07-03

首先考慮這個 \(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)\)

然後考慮轉移,分為圓點和方點:

  1. 對於圓點為根,有 \(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\)
  2. 對於方點為根,那麼就只能是有兩個不同子樹有點被選中了。
    同上一樣對 \(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;
}

相關文章