题解:P11567 建造军营 II

· · 题解

\text{Link}

联考迫真 NOIP 模拟赛 T3(

学完了 cxy 的 APIO2025 课件来做的,复杂度比原 std 优一点。

题意

给定一个 n 个点 m 条边的无向连通图 G=(V,E),再给定 k 个点对 (a_i,b_i)。对于一个连通边子图 G'=(V,E'),可以选择其中的一个边子集,一条边可选当且仅当不存在 1\le i\le k 使得存在从 a_ib_i 且经过该边的边简单路径,定义 G' 的权值为选择边子集的方案数。

求所有连通边子图的权值和,对 10^9+7 取模。

## 题解 容易发现权值计算与边双有关,具体地,将边双缩点后,对于每一对点对 $(a_i,b_i)$,将 $a_i,b_i$ 所在边双之间的所有边标黑,则可以进行选择的边为不被标黑的边。 ### 边双连通-连通变换 首先必须要会求边双连通子图数。先考虑如下问题:定义 $G=\text{Trans}(F,c)$,其中 $F,G$ 分别为集合幂级数,$c$ 为一常数。对于集合 $s$ 定义其划分 $s_{1\sim k}$ 的权值为 $c^{k-1}\prod_{i=1}^k F_{s_i}$ 再乘上用 $k-1$ 条边将它们连通的方案数,即每选择一条边合并两个连通块带来 $c$ 的贡献。需要返回的集合幂级数 $G$ 的 $s$ 项系数即为 $s$ 所有划分的权值和。 考虑按照两端点编号的最大值的顺序进行加边。设集合幂级数 $p_i$ 表示允许加两端点编号均 $\le i$ 的边时的答案。对于一个点集 $S$,考虑删去新加边中一端点为 $i$ 的边,形成若干个连通块 $S=P\cup T_1\cup T_2\cup\cdots\cup T_k$,其中 $P$ 包含点 $i$,其余连通块均不包含。 设集合幂级数 $q_{i-1,s}=[i\notin S]p_{i-1,s}\cdot cof(i,S)\cdot c$,其中 $cof(i,S)$ 表示 $i$ 与集合 $S$ 内编号 $\le i$ 的点的边数。容易发现有 $p_{i,s}=(p_{i-1}\cdot \exp q_{i-1})_s$,注意该式只有在 $i\in S$ 的项成立,对于其余项平凡地有 $p_{i,s}=p_{i-1,s}$。 令 $F_s$ 表示 $s$ 内连通子图数,则 $\text{Trans}(F,-1)$ 即为边双连通子图数的集合幂级数。考虑 $s$ 的一个子图,若其中存在割边,则这条割边必然分别在 $F_s$ 处和合并时贡献 $1$ 与 $-1$,即该方案不会被计入。求解 $F_s$ 是经典问题,直接求 $\ln $ 即可。 时间复杂度 $O(2^nn^3)$。 ### 本题解法 我们希望将原图划分为若干黑边连通块与白边双连通分量,且这些连通块之间均用白边连接。 先求出每个集合 $s$ 作为黑边连通块的方案数。令 $F_s$ 表示 $s$ 内连通子图数,注意不能存在 $a_i,b_i$ 使得其中恰有一个点在 $s$ 内。考虑 $F'=\text{Trans}(F,-1)$,则若一种方案中存在白边,其必然会被抵消。 接下来需要求出集合 $s$ 作为白边双连通分量时的权值之和。令 $G_s$ 表示 $s$ 内连通子图权值和,注意不能存在任意 $a_i$ 或 $b_i$ 在 $s$ 内。$G$ 的求解仍可用求 $\ln$ 解决,而 $G'=\text{Trans}(G,-2)$ 即为所求。 最后只需调用 $\text{Trans}(F'+G',2)$ 合并连通块即可。 时间复杂度 $O(2^nn^3)$,经卡常可获得最优解。 附一份未经卡常的代码: ```cpp const int S=1<<16,N=16+2,M=N*N,mod=1e9+7; int n,m,k,u,pw2[M],pw3[M],a[N],ct[S],c1[S],c2[S],T[S],F[S],G[S],A[S],B[S]; inline void Trans(int *F,int c){ static int A[S],G[S]; for(int i=0;i<n;i++){ for(int s=0;s<u;s++) A[s]=s>>i&1?0:1ll*c*F[s]%mod*__builtin_popcount(a[i]&s)%mod; Poly::Exp(A,A); Poly::Mul(A,F,G); for(int s=0;s<u;s++) F[s]=s>>i&1?G[s]:F[s]; } } int main(){ // freopen("ex_war4.in","r",stdin); n=read(),k=read(),u=1<<n; Prefix(u),Poly::init(u); for(int i=0;i<n;i++) for(int j=0;j<n;j++){ int c=getc()-'0'; if(i<=j||!c) continue; a[i]|=1<<j,ct[(1<<i)|(1<<j)]++; } while(k--){ int u=read()-1,v=read()-1; c1[1<<u]++,c1[1<<v]++,c1[(1<<u)|(1<<v)]-=2; c2[1<<u]++,c2[1<<v]++; } pw2[0]=pw3[0]=1; for(int i=1;i<=n*n;i++) pw2[i]=add(pw2[i-1],pw2[i-1]), pw3[i]=3ll*pw3[i-1]%mod; for(int i=0;i<n;i++) for(int s=0;s<u;s++) if(s>>i&1) ct[s]+=ct[s^(1<<i)], c1[s]+=c1[s^(1<<i)], c2[s]+=c2[s^(1<<i)]; for(int s=0;s<u;s++) T[s]=pw2[ct[s]]; Poly::Ln(T,F); for(int s=1;s<u;s++) if(!c1[s]) A[s]=F[s]; Trans(A,mod-1); for(int s=0;s<u;s++) T[s]=pw3[ct[s]]; Poly::Ln(T,F); Trans(F,mod-2); for(int s=1;s<u;s++) B[s]=c2[s]?A[s]:F[s]; Trans(B,2); write(B[u-1]); flush(); } ```