【洛谷P6016】出游

题目

题目链接: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;
}
posted @ 2020-01-29 16:12  stoorz  阅读(180)  评论(0编辑  收藏  举报