P10674 [MX-S1-T3] 电动力学 题解
提供一种比现有题解简单的 DP 方式和用到结论的证明。
首先,建立原图的圆方树,注意到如果两个圆点 \(x,y\in T\),那么在圆方树上 \(x\to y\) 路径上的所有方点对应的点双连通分量中的点都能加入 \(S\),为了证明这一点,我们需要一个观察。
观察 在一个点双连通图中,如果存在三个互异的点 \(x,y,s\),则必然存在一条从 \(x\) 到 \(y\) 的简单路径且该路径经过 \(s\)。

由点双连通图的性质,我们知道 \(x\to s\)、\(s\to y\) 分别存在两条不交的简单路径,我们令 \(x\to s\) 的分别为 \(U_1,U_2\),\(s\to y\) 的分别为 \(V_1,V_2\),现在我们说明,使用这些路径能构造出我们想要的路径。
假设存在一对 \(U_i,V_j\) 满足他们不交,那么选择这两条路径即可。假设这种路径对不存在,我们考虑取 \(U_2\),若将其上的点按从 \(x\) 到 \(s\) 的顺序排列,记作 \(a_1=x,a_2,a_3,\dots,a_k=s\),找到最小的 \(i\),使得 \(a_i\) 也存在于 \(V_1\) 或 \(V_2\) 上。不妨设该 \(a_i\) 存在于 \(V_1\) 上,那么我们可以知道首先 \(a_i\) 不可能存在于 \(V_2\) 上(否则 \(V_1,V_2\) 就有交点了),其次我们可以知道对于所有 \(j<i\),\(a_j\) 也不存在于 \(V_2\) 上,那么我们此时可以找到简单路径 \(x\stackrel{U_2}{\longrightarrow} a_i\stackrel{V_1}{\longrightarrow} s\stackrel{V_2}{\longrightarrow}y\),构造完成。命题得证。
有了这一观察,我们就很容易证明上述结论,对路径上进入点双和离开点双的点应用上述观察即可。
我们设圆方树上圆点 \(x\) 点权 \(v_x\) 为 \(1\),方点 \(x\) 点权 \(v_x\) 为 \(2^{s_x-1}\),其中 \(s_x\) 表示 \(x\) 这个方点所对应的点双的大小,结合上述结论容易说明集合 \(T\) 对答案的贡献就是 \(\prod_{x}v_x,\text{where }\exists a,b\in T,x\in\operatorname{path}(a,b)\),其中 \(\operatorname{path}(a,b)\) 表示树上 \(a\) 到 \(b\) 的简单路径。
接下来,我们考虑设 \(f_i\) 表示圆方树上以第 \(i\) 个点为根的子树中,所有非空集合 \(T\cup\{i\}\) 当前对答案的贡献之和。

也就是提前将选 \(i\) 的权值记上了,转移分情况讨论,以下记 \(v\) 为 \(x\) 的直接儿子:
- 
对于方点 \(x\),\(f_x=\left(\prod_v \left(f_v+1\right)-1\right)\times2^{s_x-1}\) 
- 
对于圆点 \(x\),\(f_x=2\left(\prod_v \left(f_v+1\right)-1\right)-1\) 
意义非常明确,应当无需赘述。对于每个集合 \(T\),我们在其所有点的 LCA 处统计它的贡献。以下记 \(x\) 的儿子集合 \(v\in S\)。
- 
对于方点 \(x\),\(ans\gets ans+\left(\sum_{P\subseteq S,|P|\ge 2}\prod_{v\in P}f_v\right)\times 2^{s_x-1}\) 说人话就是,至少选择两个子树的集合合并,保证 LCA 是 \(x\)。 
- 
对于圆点 \(x\),\(ans\gets ans+\prod_v \left(f_v+1\right)+\sum_{P\subseteq S,|P|\ge 2}\prod_{v\in P}f_v\) 说人话就是,如果选择这个点,那么子树随便怎么都行,如果不选 \(x\) 这个点,那么至少选择两个子树的集合来合并。 
这些转移非常容易维护。最后,注意到我们给权值定义为 \(2^{s_x-1}\) 的原因是要排除相邻方点之间的割点,但是现在多排除了一个点,最后需要乘以 \(2\)。另外,\(S=T=\emptyset\) 是一种合法方案,给答案加 \(1\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef long long ll;
const ll P=998244353;
ll n,m,f[N];
vector<int> e[N],rst[N];
void add(vector<int> e[N],int x,int y){e[x].push_back(y),e[y].push_back(x);
}
int tim,dfn[N],low[N],nC,siz[N];stack<int> stk;
void Tarjan(int x){low[x]=dfn[x]=++tim,stk.push(x);for(int v:e[x]){if(!dfn[v]){Tarjan(v);low[x]=min(low[x],low[v]);if(low[v]>=dfn[x]){++nC;int cur;add(rst,nC,x),siz[nC]++;do{cur=stk.top(),stk.pop();add(rst,cur,nC),siz[nC]++;}while(cur!=v);}}else low[x]=min(low[x],dfn[v]);}
}
ll pw2[N],ans;
void DP(int x,int fa){ll prod=1,sum=0;for(int v:rst[x]){if(v==fa) continue;DP(v,x),prod=prod*(f[v]+1)%P,sum=(sum+f[v])%P;}if(x>n){f[x]=(prod-1)*pw2[siz[x]-1]%P;prod=(prod-1-sum)%P;ans=(ans+prod*pw2[siz[x]-1]%P)%P;}else{ans=(ans+prod)%P,f[x]=prod*2ll%P-1;prod=(prod-1-sum)%P,ans=(ans+prod)%P;}
}
int main(){ios::sync_with_stdio(false),cin.tie(nullptr);cin>>n>>m;pw2[0]=1;for(int i=1,u,v;i<=m;i++) cin>>u>>v,add(e,u,v);for(int i=1;i<=n;i++) pw2[i]=pw2[i-1]*2ll%P;nC=n,Tarjan(1),DP(1,0);cout<<((2ll*ans%P+1)%P+P)%P;
}
做完了喵!