概率期望训练之三
http://acm.hdu.edu.cn/showproblem.php?pid=5036
题目大意:
开始你没有任何钥匙,而你面前有很多道门,每道门后面可能有打开其他门的钥匙,如果你无法打开这道门,可以选择将它炸开,求打开所有门的期望炸的次数
分析:
首先根据期望的线性性
炸开所有门的期望次数=Σ每道门的期望次数
但这道题烦就烦在有可能会有环
先建图
假如没有环的话,ans=Σ1/dep[i]
但现在有环怎么办?
你肯定和我想的一样,Tarjan缩点呗
然后就莫名其妙的wa了(应该是我写错了,正解只是更简化了)
wa code:(不想看就跳过)
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1e3+5;
bool vis[maxn];
queue<int>S;
vector<int>Q[maxn],t[maxn];
double ans;
int T,cnt,top,sum,n,ca;
int dfn[maxn],deg[maxn],sz[maxn],belong[maxn],low[maxn],stk[maxn],dep[maxn];
il void Tarjan(int u){
dfn[u]=low[u]=++cnt;vis[u]=true;
stk[++top]=u;
for(ri i=0;i<Q[u].size();i++){
int to=Q[u][i];
if(!dfn[to])Tarjan(to),low[u]=min(low[to],low[u]);
else if(vis[u])low[u]=min(dfn[to],low[u]);
}
if(dfn[u]==low[u]){
++sum;sz[sum]=0;t[sum].clear();int tp;
do{
tp=stk[top--];
vis[tp]=false;
belong[tp]=sum;
sz[sum]++;
}while(tp!=u);
}}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(ri i=1;i<=n;i++){deg[i]=dfn[i]=low[i]=dep[i]=0;
int tot;scanf("%d",&tot);
Q[i].clear();
for(ri j=1;j<=tot;j++){
int x;scanf("%d",&x);
if(x==i)continue;
Q[i].push_back(x);
}
}
cnt=top=sum=ans=0;
for(ri i=1;i<=n;i++)if(!dfn[i])Tarjan(i);
for(ri u=1;u<=n;u++)
for(ri i=0;i<Q[u].size();i++){
int to=Q[u][i];
if(belong[to]!=belong[u])t[belong[u]].push_back(belong[to]),deg[belong[to]]++;
}
for(ri i=1;i<=sum;i++)if(!deg[i]){S.push(i),dep[i]=sz[i];}
while(!S.empty()){
int tmp=S.front();S.pop();
ans+=(double)sz[tmp]/dep[tmp];
for(ri i=0;i<Q[tmp].size();i++){
int to=Q[tmp][i];
deg[to]--;dep[to]+=dep[tmp];
if(!deg[to]){
dep[to]+=sz[to];
S.push(to);
}
}
}
printf("Case #%d: %.05f\n", ++ca, ans);
}
return 0;
}
/*
2
3
1 2
1 3
1 1
3
0
0
0
*/
正解
考虑传递闭包:
所谓传递闭包就是A能够到B,B能够到C,那么A也就能够到C
考虑集合S能够到A,集合T能够到B,A能够到B,则A|S|T就能够到B
用bitset维护即可
#include <iostream>
#include <cstring>
#include <cstdio>
#include <bitset>
#include <algorithm>
using namespace std;
const int maxn = 1e3 + 5;
bitset<maxn> v[maxn];
int main()
{
int t, ca = 1, n, k, x;
cin >> t;
while(t--)
{
scanf("%d", &n);
for (int i = 0; i <= n; i++)
v[i].reset(),v[i][i] = true;//它自己也要算上
for(int i = 1; i <= n; i++)
{
scanf("%d", &k);
for(int j = 1; j <= k; j++)
scanf("%d", &x),v[i][x] = 1;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(v[j][i])
v[j] |= v[i];//传递闭包
double ans = 0;
for(int i = 1; i <= n; i++)
{
int cnt = 0;
for(int j = 1; j <= n; j++)
if(v[j][i]) cnt++;
ans += 1.0 / cnt;
}
printf("Case #%d: %.05f\n", ca++, ans);
}
return 0;
}