题目
题目链接:https://www.luogu.com.cn/problem/P6016
学校组织了一次暑期出游活动,报名将在第 \(T\) 天截止。
一共有 \(n\) 位同学,第 \(i\) 位同学有 \(a_i\) 位朋友。朋友关系是单向的,换句话说,小 Z 有一个朋友是小 Y,并不意味着小 Y 一定也有一个朋友是小 Z。另外,自己也可能是自己的朋友。
第 \(0\) 天时,每位同学会决定自己是否参加活动。第 \(i\) 位同学有 \(p_i\) 的概率决定参加,\(1-p_i\) 的概率决定不参加。
接下来的 \(T\) 天里,每位同学会重新决定自己是否参加活动。第 \(i\) 位同学这一天决定参加活动,当且仅当至少有一个他的朋友在前一天决定参加,否则便不参加。
你需要求出参加活动的同学人数期望,答案对 \(998244353\) 取模。
思路
我们设\(f[k][i][j]\)表示在第\(k\)天,同学\(i\)的选择是否会影响到同学\(j\)的选择。那么明显有转移
\[f[k][i][j]=(f[k-1][i][1]\texttt{ and } f[k-1][1][j])\texttt{ or ... or}(f[k-1][i][n]\texttt{ and } f[k-1][n][j])
\]
时间复杂度\(O(Tn^3)\),矩阵乘法优化后\(O(n^3\log T)\)。
由于上述转移包含位运算,所以我们可以用\(bitset\)优化,如下
struct matrix
{
bitset<N> a[N];
//a.a[i][j]表示在当前步数下,i是否对j做贡献
friend matrix operator *(matrix &a,matrix &b)
{
matrix c;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (a.a[i][j]) c.a[i]|=b.a[j];
//如果在当前步数下,i对j有贡献,那么在下一步,i就能对这一步j能做贡献的点做贡献
return c;
}
};
这样的话时间复杂度就降到了\(O(\frac{n^3\log T}{32})\),可以过掉本题。
我们处理出\(T\)步之后的贡献后,得到一个矩阵\(f\)。再枚举两个点\(i,j\),如果\(f.a[j][i]=1\),那么\(j\)就对\(i\)在\(T\)天之后有贡献。
那么如何计算\(T\)天后一个人去的期望呢?
显然有
\[1-q[i]=\Pi_{f.a[j][i]=1}(1-p[i])
\]
那么直接在\(\mod\ 998244353\)的意义下计算答案即可。
代码
#include <cstdio>
#include <bitset>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=510,MOD=998244353;
int n,m,ans,p[N];
struct matrix
{
bitset<N> a[N];
friend matrix operator *(matrix &a,matrix &b)
{
matrix c;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (a.a[i][j]) c.a[i]|=b.a[j];
return c;
}
}a,f;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<=n;i++)
{
f.a[i][i]=1;
scanf("%d%d",&p[i],&x);
while (x--)
{
scanf("%d",&y);
a.a[y][i]=1;
}
}
for (;m;m>>=1,a=a*a)
if (m&1) f=f*a;
for (int i=1;i<=n;i++)
{
int x=1;
for (int j=1;j<=n;j++)
if (f.a[j][i])
x=1LL*x*(1-p[j])%MOD;
ans=(ans+1-x)%MOD;
}
printf("%d",(ans%MOD+MOD)%MOD);
return 0;
}