0%

[CF2029H] Message Spread

好题喵。

题目链接

题意

给定一张简单连通无向图,每一条边每一个时刻有 的概率出现。初始的时候节点 有一条消息。

对于一个没有消息的点 ,若某一个现在与 相邻的点在上一时刻有消息,则点 这一时刻会获得消息。

题解

表示,恰好只有 内部节点有消息,这个状态有多少的概率会经过。

表示若恰好只有 这些点有消息,经过多少个时刻之后,会有新的节点接受到消息。

那么答案就是

那么 很容易 算出来,只需要看一下 向外连的边即可。

表示当前状态是,经过一个时刻,新增了 这些点获得信息的概率。

考虑 怎么转移,枚举下一次有新增的点的时候,新增的点集合为

那么就有转移

精细实现一下就能做到

听说这个能过,真的假的

,和 即全集。

考虑 的转移,此时只需要下个时刻 的点里面没有连边即可。

那么设 表示经过一个时刻, 这些点没有获得信息的概率,即要求 没有边。

就有转移

考虑 这种东西如何表示,设 表示

考虑能不能只用 这种东西把 表示出来。

那么推一下能发现

往回带,就能得到转移式

减号后面只和 有关,而减号前面 都有

对于后面的部分,可以看出,这是一个半在线子集卷积,首先尝试回忆普通的子集卷积。


两个序列 的子集卷积满足

那么我们把 都按照 进行分类。

具体的,令 同理。

那么将序列 做或卷积,得到的序列中 的部分就是 的部分。

直接做,需要跑 次或卷积,复杂度是

考虑优化 FMT 的次数, 的正 FMT 可以提前预处理好,复杂度是

卷完以后还需要进行逆变化,直接把所有 相同的放一起逆变换,复杂度是

这样就在 复杂度内完成了普通的子集卷积。


那么再考虑这种半在线的子集卷积呢,能发现,还是可以参照上面的过程。

假设我们当前需要计算 的半在线卷积。

考虑从小到大枚举数字 。那么我们先计算 的或卷积正变化结果。

这样就做完了半在线的子集卷积,半在线的或卷积也同理。

这样就得到了 ,进而能得到

复杂度

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
template<typename A,const int N>
using aya=array<A,N>;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline ll lread()
{
ll s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int mod=998244353;
const int N=2097152;
ll P[N];
ll Q[N];
ll f[N];
ll g[N];

ll a[22][N];
ll b[22][N];
ll C[N];

ll D[N];
ll E[N];

int num[N];
int n,m;
inline ll qow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
inline void Add(ll &a,ll b){ a+=b;if(a>=mod) a-=mod;}
inline void FMT(ll *f,int s=0)
{
for(int i=s;i<n;i++) for(int j=s|(1<<i);j<(1<<n);j=(j+(1<<s))|(1<<i)) Add(f[j],f[j^(1<<i)]);
}
inline void IFMT(ll *f)
{
for(int i=1;i<n;i++) for(int j=(1<<i)+1;j<(1<<n);j=(j+2)|(1<<i)) Add(f[j],mod-f[j^(1<<i)]);
}
int main()
{
n=read(),m=read();int U=(1<<n)-1;
for(int i=0;i<(1<<n);i++) P[i]=Q[i]=1,num[i]=__builtin_popcount(i);
for(int i=1;i<=m;i++)
{
int u=read()-1,v=read()-1,p=read(),q=read();
ll w=p*qow(q,mod-2)%mod;w=(1-w+mod)%mod;
int id=(1<<u)|(1<<v);

P[id]=P[id]*w%mod;
Q[1<<u]=Q[1<<u]*w%mod;
Q[1<<v]=Q[1<<v]*w%mod;
Q[id]=Q[id]*qow(w*w%mod,mod-2)%mod;
}
for(int i=0;i<n;i++) for(int j=(1<<i);j<(1<<n);j=(j+1)|(1<<i)) if(j&(1<<i))
{
P[j]=P[j]*P[j^(1<<i)]%mod;
Q[j]=Q[j]*Q[j^(1<<i)]%mod;
}
f[1]=1;
for(int k=1;k<n;k++)
{
for(int i=1;i<(1<<n);i+=2) if(num[i]==k)
{
ll V=f[i]*qow(mod+1-Q[i],mod-2)%mod;
D[i]=V*Q[i]%mod;
if(i>1) D[i]=(D[i]+f[i])%mod;
a[k][i]=V*qow(P[i],mod-2)%mod;
}
for(int i=0;i<(1<<n);i+=2) if(num[i]==k) b[k][i]=P[i^U];
memcpy(E,D,sizeof(D));
FMT(E,1),FMT(a[k],1),FMT(b[k]);

memset(C,0,sizeof(C));
for(int i=1;i<=k;i++) for(int j=1;j<(1<<n);j+=2) C[j]=(C[j]+a[i][j]*b[k+1-i][j])%mod;
IFMT(C);
for(int i=1;i<(1<<n);i+=2) if(num[i]==k+1)
{
f[i]=C[i]*qow(P[U^i],mod-2)%mod;
f[i]=(f[i]-E[i]+mod)%mod;
}
}

ll ans=0;
for(int i=1;i<U;i+=2)
{
g[i]=qow((mod+1-Q[i])%mod,mod-2);
ans=(ans+f[i]*g[i])%mod;
}
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
return 0;
}

感谢观看!