题解 P6016 【出游】
万弘
·
·
题解
//upd:修复了最后时间复杂度分析的错误,增加代码注释
首先,如果A发生的概率p_a=\frac{b1}{a1}+\frac{b2}{a2}
且\frac{b1}{a1}\equiv p_1(mod\ P),\frac{b2}{a2}\equiv p_2(mod\ P)
那么p_a\equiv p_1+p_2(mod\ P)
所以概率/期望加法的时候,直接加然后取模就可以了,不用求逆元之类.
接下来开始讲做法:
先特判T=0:
第一天的概率即为最后一天的概率.每一个人的贡献就是p_i,总期望值E=\sum_{i=1}^n1*p_i=\sum_{i=1}^np_i
记$trans_{i,j}$表示当$i$去则下一天$j$是否必须去(是则为1,否则为0)
考虑$i$最后去的概率$last_i$的反面,即$i$不去.这需要所有他的朋友前一天都没去.那么
$$1-last_i=\prod_{trans_{j,i}=1} (1-p_j)$$
暴力求即可,时间复杂度$O(n^2)
算法1:
设trans_{i,j}^k表示如果i第0天去了,j在第k天是否必须去.
那么
trans_{i,j}^k=OR_{p=1}^n trans^{k-1}_{i,p}and\ trans_{p,j}
这里OR是我自创的符号,类似于\sum,即(trans^{k-1}_{i,1}and\ trans_{1,j})\ or\ (trans^{k-1}_{i,2}and\ trans_{2,j})\ or\ (trans^{k-1}_{i,3}and\ trans_{3,j})...or\ (trans^{k-1}_{i,n}and\ trans_{n,j}))
嗯于是暴力递推计算这个式子,最后把trans_{i,j}^k算出,时间复杂度O(n^3T)
最后的式子变成
1-last_i=\prod_{trans_{j,i}^k=1} (1-p_j)
似乎还是同样的17pts
算法2:
求trans的式子非常类似矩阵乘法.并且or运算满足结合律,所以使用矩阵快速幂优化.时间复杂度O(n^3logT)
关键部分:
#define MAXN 511
struct mat
{
bool a[MAXN][MAXN];
void operator *=(const mat& t)
{
bool res[MAXN][MAXN];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
res[i][j]=0;
for (int k = 1; k <= n; ++k)
res[i][j]|=(a[i][k]&t.a[k][j]);
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
a[i][j]=res[i][j];
}
}trans;
......
mat Qpow(ll m)//矩阵快速幂,m是乘的次数,即T
{
--m;//注意T=0要特判掉的
mat res=trans,a=trans;
while(m)
{
if(m&1)res*=a;
a*=a;
m>>=1;
}
return res;//即trans^k
}
时间复杂度O(n^3logT),直接就85pts了,很优秀.
算法3:
再观察下矩阵乘法那里.整个矩阵都是01矩阵,那么每一行,每一列都是01向量.考虑每一行压进一个bitset,加快乘法效率.
先把程序中的t按对角线对称得到t',即t'[j][k]=t[k][j]
那么
res[i][j]=OR_{p=1}^na[i][k]\ and\ t'[j][k]
$$res[i][j]=a[i]\ and\ t'[j]$$
(注意$res[i][j]$是bool,$a[i]\ and\ t'[j]$是bitset,所以这里赋值符应该是$a[i]\ and\ t'[j]$中是否有1)
矩阵乘法的时间复杂度优化为$O(\frac{n^3}{w})$,总时间复杂度$O(\frac{n^3logT}{w})$,AC.
代码存在一些变量名于题解不一致的情况,读者不必拘泥于代码
```cpp
/**********/省略快读
#define MAXN 511
const ll mod=998244353;
ll n,m;//人数,天数
struct mat
{
std::bitset<MAXN>a[MAXN];
void operator *=(const mat& t)//bitset优化矩阵乘法
{
std::bitset<MAXN>res[MAXN],tmp[MAXN];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
tmp[i][j]=t.a[j][i];
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
res[i][j]=(a[i]&tmp[j]).any();
}
for (int i = 1; i <= n; ++i)
a[i]=res[i];
}
}trans;
ll p[MAXN],f[MAXN];//初始概率,结束概率(即1-last)
ll update(ll x)//对mod取模
{
return (x%mod+mod)%mod;
}
......
mat Qpow(ll m)//矩阵快速幂
{
--m;
mat res=trans,a=trans;
while(m)
{
if(m&1)res*=a;
a*=a;
m>>=1;
}
return res;
}
int main()
{
n=read(),m=read();
for (int i = 1; i <= n; ++i)
{
p[i]=read();f[i]=1;(一开始是1,即假设不去)
ll x=read();
while(x--)
{
ll v=read();
trans.a[v][i]=1;//如果v去,i必须去
}
}
if(m==0)
{
ll ans=0;
for (int i = 1; i <= n; ++i)ans=update(ans+p[i]);
printf("%lld",ans);
return 0;
}
mat g=Qpow(m);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if(g.a[i][j])f[j]=update(f[j]*(1-p[i]));//上面的式子反过来,变成i对j的贡献(实现的时候这样写了,但写题解的时候发现那样更好理解
ll ans=0;
for (int i = 1; i <= n; ++i)ans=update(ans+1-f[i]);//最后直接求和了
printf("%lld",ans);
return 0;
}
```