【小学期实训】附加题题解——Good Karma
[状压dp+容斥原理] 实训附加题——Good Karma
题目描述
题目
「天空度假山庄」中有一个 \(n\) 点 \(m\) 边的无向图,图中点的编号分别为 \(1,2,⋯ ,n\),边的编号分别为 \(1,2,⋯ ,m\)。
度假山庄已经很久没有打扫了,因此图中的边已经模糊不清。
具体来说:
- 第 \(i\) 条边连接的两个端点分别为 \(u_i\) 和 \(v_i\)。
- 这条边上落下了许多灰尘,因此云浅有 \(p_i\) 的概率看不到这条边。这里保证 \(0≤p_i≤1,p_i∈Q\)。
在云浅看到的图中,若 \(1\) 号点所在的连通块大小为 \(x\),则云浅会认为这张图的「美观程度」为$ x^2$。
云浅想要求出这张图的「美观程度」的期望值。她还要帮 \(Oshiro\) 打扫山庄,因此她找到了你求助。
为了避免精度误差,本题的输入输出格式较为特殊,详细信息将在【输入格式】与【输出格式】中说明。
输入格式
第一行两个正整数 \(n,m\)。
接下来 \(m\) 行,第 \(i+1\) 行有 \(4\) 个正整数 \(ui,vi,xi,yi\),表示一条边。其中:
- \(u_i,v_i\) 表示这条边的两个端点。
- 云浅看不到这条边的概率即为 \(p_i=\frac{x_i}{x_i+y_i}\)。
输出格式
一行一个正整数表示这张图的「美观程度」的期望值。
为了避免精度误差,你只需要求出答案对 \(998244353\) 取模后的值即可。
更具体地,设答案为 \(\frac{A}{B}\),其中 \(gcd(A,B)=1\),你需要输出一个 \(C\) 使得 \(B×C≡A ( mod \text{ }998244353)\)。
可以证明这样的 \(C\) 一定存在且唯一。
数据范围
对于 \(100\%\) 的数据,\(1≤n≤17,1≤m≤\frac{n(n−1)}{2},1≤x_i,y_i<998244353,x_i+y_i≠998244353\)。
测试点编号 | \(n\) | \(m\) |
---|---|---|
\(1∼2\) | \(=8\) | \(≤12\) |
\(3∼4\) | \(=9\) | \(≤12\) |
\(5∼6\) | \(=10\) | \(≤15\) |
\(7∼8\) | \(=11\) | \(≤25\) |
\(9∼10\) | \(=12\) | \(≤30\) |
\(11∼12\) | \(=13\) | \(≤40\) |
\(13∼14\) | \(=14\) | \(≤\frac{n(n−1)}{2}\) |
\(15∼16\) | \(=15\) | \(≤\frac{n(n−1)}{2}\) |
\(17∼18\) | \(=16\) | \(≤\frac{n(n−1)}{2}\) |
\(19∼20\) | \(=17\) | \(≤\frac{n(n−1)}{2}\) |
格式说明
输出时每行末尾的多余空格,不影响答案正确性
输入、输出要求
要求使用「文件输入、输出」的方式解题,输入文件为
karma.in
,输出文件为karma.out
样例输入1
4 4
1 2 1 1
2 3 1 2
3 1 1 3
2 4 2 3
样例输出1
465847375
样例输入2
2 2
1 2 1 8
2 1 2 4
样例输出2
554580200
样例解释2
图拓扑为 \(1\) 和 \(2\) 之间连接了两条边。
设 \(1\) 所在连通块大小为 \(x\)。
连接 \(1\) 和 \(2\) 的两条边不被看见的概率分别为 \(\frac{1}{9}\) 和 \(\frac{1}{3}\)。因此 \(x^2=1\) 当且仅当两条边都不被看见,概率为 \(\frac{1}{27}\)。\(x^2=4\) 以概率 \(\frac{26}{27}\) 取到。
故答案为\(\frac{1}{27}×1+\frac{26}{27}×4=\frac{35}{9}\).
由于 \(9×554580200≡35 ( mod \text{ }998244353)\),因此你应该输出 \(554580200\)。
Solution
注意到 \(n\le 17\) ,并且题目与集合的枚举(要求枚举与 \(1\) 连通的连通块)有关,容易想到用状压dp来做这道题。
解决这道题目的关键,在于状态如何设计以及如何进行转移
设 \(dp_S\) 表示 最终与点 \(1\) 连通的点集为 \(S\) (其中对于边 \((u,v)\) 的端点 \(u\) 和 \(v\),必须满足 \(u\) 和 \(v\) 都在集合 \(S\) 当中),\(S\) 内部连通的概率 ,\(S\) 之外的点的连通性不考虑在其中。
如何求出 \(dp_S\) ?
——我们可以枚举符合条件的 \(S\) 的子集 \(T\) :考虑 \(S\) 中 包含点 \(1\) 的 真子集 \(T\) ,\(T\) 是连通的,而 \(T\) 和 \(S-T\) (\(\complement_ST\))不能有连通的点
因此我们可以构建一个辅助数组 \(g\) , \(g(A,B)\) 表示 点集 \(A\) 与点集 \(B\) 不连通的概率,\(g(T,S-T)\) 即为我们需要的。
如何求 \(g(A,B)\) ? 实际上,\(g(A,B)\) 其实就是 对于 \(\forall u \in A,v \in B\),边 \((u,v)\) 必须不连通。题目中 \(p(u,v)\) 指的是 边 \((u,v)\) 不连起来的概率。形式化地来描述,\(g(A,B)=\displaystyle \prod_{u\in A \bigwedge v \in B} p(u,v)\)
如果要预处理出 \(g(A,B)\) ,需要 \(O(4^n)\) 的空间,显然不现实。
注意到 \(g(A,B)\) 其实就是 \(A\) 与 \(B\) 之间的任意一条边都不连通。可以再设一个辅助数组 \(w(A)\) 表示集合 \(A\) 中任意一条边都不连起来, \(w(A)\) 比较好求
即有 \(g(A,B) = \frac{w(A \cup B)}{w(A) \times w(B)}\) ,很容易理解:\(w(A \cup B)\) 表示 集合 \(A\cup B\) 中任意一条边都不连起来 ,从其中除去 \(w(A)\) ( \(A\) 中任意一条边都不连起来) 和 \(w(B)\) ( \(B\) 中任意一条边都不连起来) ,得到的结果就是 \(A\) 与 \(B\) 之间的任意一条边都不连起来的概率。
最后我们可以得到 \(dp_S\) 的转移方程 : \(dp_S=1-\displaystyle \sum^{m}_{1\in T \bigwedge T \subsetneq S} dp_T\times g(T,S-T)\)
答案则是:\(ans=\displaystyle \sum^{}_{1\in S} dp_S \times g(S,U-S) \times |S|^2\),\(dp_S\) 是\(S\) 内部连通的概率,乘上 \(g(S,U-S)\) 表示不与 \(S\) 之外的点连通,乘上 \(|S|^2\) 即为题目所求的期望。
附上代码:
#include <stdio.h>
#define N (17+5)
#define ST ((1<<17)+5)
typedef long long LL;
int n,m,sz;
LL mp[N][N];
LL w[ST],invw[ST];
LL dp[ST];
const LL p=998244353;
LL qpow(LL a,LL b,LL p){
LL res=1;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
LL inv(LL a,LL p){
return qpow(a,p-2,p);
}
void get_w(){ //求w数组
for(int k=0;k<=sz;k++){
w[k]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
for(int k=0;k<=sz;k++){
if(k&(1<<(i-1))||k&(1<<(j-1))) continue;
int st=k;
st|=(1<<(i-1)),st|=(1<<(j-1)); //(i,j)边不连通
w[st]=(w[st]*mp[i][j])%p; //乘进w[st]中
}
}
}
for(int k=0;k<=sz;k++){ //预处理出逆元
invw[k]=inv(w[k],p);
}
}
LL g(int a,int b){ //求辅助函数g
return w[a+b]*invw[a]%p*invw[b]%p;
}
void solve(){
for(int i=1;i<=sz;i+=2){ //枚举包含点1的集合S
dp[i]=0;
for(int j=(i-1)&i;j;j=(j-1)&i){ //枚举S的子集T
if((j&1)==0) continue; //T也要包含点1
dp[i]=(dp[i]+(dp[j]*g(j,i-j))%p)%p;
}
dp[i]=(1-(dp[i])+p)%p;
}
LL ans=0;
for(int i=1;i<=sz;i+=2){
int k=0,st=i;
while(st){
if(st&1) k++;
st>>=1;
}
ans=(ans+dp[i]*g(i,sz-i)%p*k%p*k%p)%p;
}
printf("%lld\n",ans);
}
int main(){
freopen("karma.in","r",stdin);
freopen("karma.out","w",stdout);
scanf("%d%d",&n,&m);
int u,v;
LL x,y;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
mp[i][j]=1;
}
}
for(int i=1;i<=m;i++){
scanf("%d%d%lld%lld",&u,&v,&x,&y);
if(u==v) continue;
mp[u][v]=mp[u][v]*x%p*inv((x+y)%p,p)%p;
mp[v][u]=mp[u][v];
}
sz=(1<<n)-1;
get_w();
solve();
return 0;
}