给定 n ( 1 ≤ n ≤ 16 ) n(1\le n\le 16) n(1≤n≤16) 个点 m ( 0 ≤ 100 ) m(0\le 100) m(0≤100) 条正边权的无向简单图,求每个生成子图的最小生成森林的权值和,答案对 998244353 998244353 998244353 取模。
定义点集 V V V ,边集 E E E 的图 G G G 的最小生成森林为:
图 G G G 的生成子图为具有点集 V V V 和边集为 E E E 的子集所构成的图。
考虑枚举,显然生成子图和最小生成森林都不易枚举,不妨枚举每一条边,计算其在多少张生成子图中作为最小生成森林的边(即作贡献的次数)。
不妨将所有
m
m
m 条边按照边权从小到大进行排序,考虑第
i
i
i 条边
e
i
e_i
ei 的贡献
a
n
s
i
ans_i
ansi 。
(
u
i
,
v
i
u_i,v_i
ui,vi 表示
e
i
e_i
ei 的两个端点,
w
i
w_i
wi 表示
e
i
e_i
ei 的权值)
显然,边权大于
w
i
w_i
wi 的边(即排序后下标
j
∈
[
i
+
1
,
m
]
j\in [i+1,m]
j∈[i+1,m] 的边)是否存在于生成子图中不会对
e
i
e_i
ei 是否作贡献造成影响,只需要在算出前
i
i
i 条边组成的生成子图中
e
i
e_i
ei 的贡献次数后,乘上系数
2
m
−
i
2^{m-i}
2m−i (
m
−
i
m-i
m−i 条边选/不选)即可。
(对于权值相等的边而言,因为答案计算的是权值和,故考虑谁先谁后并不影响,不需要处理相互顺序,可以任意排列)
对于边权小于
w
i
w_i
wi 的边(即排序后下标
j
∈
[
1
,
i
−
1
]
j\in [1,i-1]
j∈[1,i−1] 的边),当且仅当他们无法使
u
i
u_i
ui 与
v
i
v_i
vi 联通时,
w
i
w_i
wi 作贡献。
不联通较难计算,正难则反,考虑稍易的
u
i
u_i
ui 与
v
i
v_i
vi 联通的生成子图数。
用状压
d
p
dp
dp 计数,设
f
i
,
S
f_{i,S}
fi,S 表示仅含有前
i
i
i 条边的生成子图中恰有
S
S
S (
S
S
S 为一个二进制压缩的点集)一个连通块的方案数。
考虑从
f
i
−
1
,
S
f_{i-1,S}
fi−1,S 转移到
f
i
,
S
f_{i,S}
fi,S ,显然,原先每一种已经联通的方案都可以乘上系数
2
2
2 (第
i
i
i 条边选/不选),并且当第
i
i
i 条边联通
2
2
2 个原先不联通的连通块时也会产生新的方案,可得转移式如下:
f
i
,
S
=
2
f
i
−
1
,
S
+
∑
T
⊂
S
,
u
i
∈
T
,
v
i
∈
S
−
T
f
i
−
1
,
T
∗
f
i
−
1
,
S
−
T
f_{i,S}=2f_{i-1,S}+\sum_{T\subset S,u_i\in T,v_i\in S-T}f_{i-1,T}*f_{i-1,S-T}
fi,S=2fi−1,S+T⊂S,ui∈T,vi∈S−T∑fi−1,T∗fi−1,S−T
然后考虑如何计算 a n s i ans_i ansi 。
辅助计数,引入
g
i
,
S
g_{i,S}
gi,S 表示前
i
i
i 条边中端点均在点集
S
S
S 中的边数。
易得转移式:
g
i
,
S
=
g
i
−
1
,
S
+
[
u
i
∈
S
&
v
i
∈
S
]
g_{i,S}=g_{i-1,S}+[u_i\in S\& v_i\in S]
gi,S=gi−1,S+[ui∈S&vi∈S]
枚举 u i , v i u_i,v_i ui,vi 联通时所在的联通块,此时有三种边:
因而可得前
i
−
1
i-1
i−1 条边构成联通子图中
u
i
,
v
i
u_i,v_i
ui,vi 不联通的个数为
2
i
−
1
−
∑
S
⊂
V
,
u
i
∈
S
,
v
i
∈
S
f
i
−
1
,
S
∗
2
g
i
−
1
,
V
−
S
2^{i-1}-\sum_{S\subset V,u_i\in S,v_i\in S}f_{i-1,S}*2^{g_{i-1,V-S}}
2i−1−S⊂V,ui∈S,vi∈S∑fi−1,S∗2gi−1,V−S
最终答案:
a
n
s
i
=
w
i
∗
2
m
−
i
∗
(
2
i
−
∑
S
⊂
V
,
u
i
∈
S
,
v
i
∈
S
f
i
−
1
,
S
∗
2
g
i
−
1
,
V
−
S
)
ans_i=w_i*2^{m-i}*(2^i-\sum_{S\subset V,u_i\in S,v_i\in S}f_{i-1,S}*2^{g_{i-1,V-S}})
ansi=wi∗2m−i∗(2i−S⊂V,ui∈S,vi∈S∑fi−1,S∗2gi−1,V−S)
实际上 f f f 和 g g g 在转移时只需要上一维,可以压缩空间,不过注意 f f f 在转移时要延迟更新。
#include
using namespace std;
template<class T>inline void read(T&x){
char c,last=' ';
while(!isdigit(c=getchar()))last=c;
x=c^48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
if(last=='-')x=-x;
}
const int N=16,M=1e2+5,P=998244353;
int n,m;
int f[(1<<N)+5],d[(1<<N)+5];
int g[(1<<N)+5];
int pw[M];
struct node{int u,v,w;}e[M];
bool cmp(node a,node b){return a.w<b.w;}
int main()
{
read(n);
for(int i=0;i<n;++i){
for(int j=0,x;j<n;++j){
read(x);
if(i>j&&x){
++m;
e[m].u=i,e[m].v=j,e[m].w=x;
}
}
}
sort(e+1,e+m+1,cmp);
for(int i=0;i<n;++i)f[1<<i]=1;//初始化,每个点单独构成联通块
pw[0]=1;
for(int i=1;i<=m;++i)pw[i]=pw[i-1]*2ll%P;//预处理2的幂
int ans=0;
for(int i=1,u,v,w;i<=m;++i){
u=e[i].u,v=e[i].v,w=e[i].w;
int S=(1<<n)-1;
S^=1<<u,S^=1<<v;//先去除u和v
int tmp=pw[i-1];
for(int T=S;~T;T=T?T-1&S:-1){//这一行是枚举所有S的子集,~T判断T是否为-1
tmp=(tmp-1ll*f[T|1<<u|1<<v]*pw[g[S^T]])%P;
//T|1<
}
ans=(ans+1ll*w*pw[m-i]%P*tmp)%P;
for(int T=S;~T;T=T?T-1&S:-1){//枚举S的子集
for(int t=T;~t;t=t?t-1&T:-1){//枚举T的子集
d[T|1<<u|1<<v]=(d[T|1<<u|1<<v]+1ll*f[t|1<<u]*f[T^t|1<<v])%P;
//d数组用于延迟更新f,t|1<
}
}
for(int T=S;~T;T=T?T-1&S:-1){//枚举S的子集
f[T|1<<u|1<<v]=(2ll*f[T|1<<u|1<<v]+d[T|1<<u|1<<v])%P;
d[T|1<<u|1<<v]=0;//清空
++g[T|1<<u|1<<v];//更新g数组
}
}
cout<<(ans+P)%P<<'\n';
return 0;
}