[DP记录]P6789 寒妖王
command_block
2021-10-21 07:42:19
**题意** : 给定 $n$ 个点 $m$ 条边的无向图,边有边权。
每条边有 $1/2$ 的概率被删除,求最后这张图的最大基环树森林(可以有普通树)的边权和的期望。
答案对 $998244353$ 取模。
$n\leq 15,m\leq 60$ ,时限$\texttt{2s}$。
------------
计算各个方案的权值和然后除以 $2^m$ 即可。
注意到“生成基环树”这一限制和“生成树”这一限制都具有拟阵性质,于是用类似 Kruskal 的算法可以求解“最大生成基环森林”。
于是我们就得到了 $O(2^m{\rm poly}(m))$ 的暴力。
不妨设边权互不相等,将边权从大到小排序,对每条边 $l_i:(u,v)$ 分别考虑不被加入答案的充要条件。
考虑比这条边权值更大的边集 $l_{1\sim i-1}$ :
- 这条边两侧是同一个基环树。(即联通,但不是一棵树)
- 这条边两侧是两个不同的基环树。
对于第一类情况,正难则反,枚举边 $u,v$ 所在的点集 $S\supseteq \{u,v\}$ ,用构成极大连通图的方案数减去构成极大生成树的方案数。
记 $f_{i,S},g_{i,S}$ 分别表示只考虑编号 $\geq i$ 的,且与点集 $S$ 有交(而非完全包含)的所有边,使得 $S$ 形成极大 连通图/树 的方案数。
(换句话说,若某种方案 $S,U-S$ 之间有边,不会被计入 ,因为不是极大的)
- 对于 $S\supseteq \{u_1,v_i\}$ 的 $S$ ,有转移 :
$$f_{i,S}=2f_{i-1,S}+\sum\limits_{u_i\in A,v_i\in B,A+B=S}f_{i-1,A}f_{i-1,B}$$
$$g_{i,S}=g_{i-1,S}+\sum\limits_{u_i\in A,v_i\in B,A+B=S}g_{i-1,A}g_{i-1,B}$$
分两类讨论,不加入或加入。其中连通图内部多边没关系,但是树内部不能多边。
- 若 $u_i,v_i$ 都不在 $S$ 内,由定义,不考虑这条边,无贡献。
- 若 $u_i,v_i$ 其中之一在 $S$ 内,考虑这条边,但一旦这条边出现, $S$ 必不是极大联通块,仍无贡献。
第一类方案数为
$$\sum_{S}2^{out_{i-1,S}}(g_{i-1,S}-f_{i-1,S})$$
其中 $out_{i,S}$ 为 $l_{1\sim i}$ 中与 $S$ 不交的边数,即考虑其余边的加入情况。
第二类方案数需要枚举 $S$ 并拆成两个不交的集合,为
$$\sum_{S}2^{out_{i-1,S}}\sum\limits_{u_i\in A,v_i\in B,A+B=S}(f_{i-1,A}-g_{i-1,A})(f_{i-1,B}-g_{i-1,B})$$
记两类方案总数为 $o$ ,注意前面的边方案数为 $2^{i-1}$ ,后面的边方案数为 $2^{m-i}$ ,于是 $l_i$ 被计入的总方案数为 $2^{m-i}(2^{i-1}-o)$ 。
复杂度为 $O(3^n*m)$ ,适当地使用剪枝和滚动数组可以大幅减小常数。
```cpp
#include<algorithm>
#include<cstdio>
#include<vector>
#define MaxM 65
#define MaxS 33000
using namespace std;
const int mod=998244353;
int f[MaxS],g[MaxS],out[MaxS];
struct Line{int u,v,w;}l[MaxM];
bool cmp(const Line &A,const Line &B)
{return A.w>B.w;}
int n,m,pw2[MaxM];
int main()
{
scanf("%d%d",&n,&m);
pw2[0]=1;
for (int i=1;i<=m;i++)pw2[i]=(pw2[i-1]<<1)%mod;
for (int i=1;i<=m;i++){
scanf("%d%d%d",&l[i].u,&l[i].v,&l[i].w);
l[i].u--;l[i].v--;
}
sort(l+1,l+m+1,cmp);
int ans=0,lim=1<<n;
for (int i=0;i<n;i++)f[1<<i]=g[1<<i]=1;
for (int k=1;k<=m;k++){
int u=l[k].u,v=l[k].v,buf=0;
for (int S=lim-1;S;S--)
if ((S>>u&1)&&(S>>v&1)){
int S2=S^(1<<u)^(1<<v),buf2=f[S]-g[S];
f[S]=(f[S]<<1)%mod;
for (int A2=S2;;A2=(A2-1)&S2){
int A=A2|(1<<u),B=S^A;
buf2=(buf2+1ll*(f[A]-g[A])*(f[B]-g[B]))%mod;
f[S]=(f[S]+1ll*f[A]*f[B])%mod;
g[S]=(g[S]+1ll*g[A]*g[B])%mod;
if (!A2)break;
}buf=(buf+1ll*pw2[out[S]]*buf2)%mod;
}else if (!(S>>u&1)&&!(S>>v&1))out[S]++;
ans=(ans+1ll*l[k].w*(pw2[k-1]-buf)%mod*pw2[m-k])%mod;
}
int inv2=(mod+1)/2;
for (int i=1;i<=m;i++)ans=1ll*ans*inv2%mod;
printf("%d",(ans+mod)%mod);
return 0;
}
```