[SHOI2011] 扫雷机器人 题解
[SHOI2011] 扫雷机器人 题解
前言
显然,一个炸弹可以引爆另一个,而另一个炸弹却不一定能引爆这一个。
考虑假设炸弹一能引爆炸弹二,则建一条一到二的有向边。
推导过程
期望,就是平均值,也就是所有情况的权值总和除以情况总数的值。
可是,情况总数足足有 n ! n! n! 种,显然会超时到没边。
当你走投无路的时候,可以尝试化简式子。
令全部的引爆次数为 c n t cnt cnt,期望为 a n s ans ans,可以写出以下式子:
a n s = c n t n ! ans=\frac{cnt}{n!} ans=n!cnt
考虑 c n t cnt cnt 该如何计算。
考虑每个炸弹对于 c n t cnt cnt 的贡献。
显然这个炸弹被手动引爆一次,那么 c n t cnt cnt 就会多 1 1 1。
考虑什么时候这个炸弹能够被手动引爆。
显然,每个炸弹都有若干个炸弹可以将其引爆,所以为了被手动引爆,这个炸弹的引爆时间必须在能引爆它的炸弹的前边。
令对于第 i i i 个炸弹,有 k i k_i ki 个炸弹可以将它引爆,为了方便计算,此处的 k i k_i ki 包含第 i i i 个炸弹本身。
显然第 i i i 个炸弹被手动引爆的概率 s i s_i si 如下:
s i = 1 k i ! s_i=\frac{1}{k_i!} si=ki!1
那么第 i i i 个炸弹能够被手动引爆的次数 t i t_i ti 为:
t i = 1 k i ! ⋅ n ! t_i=\frac{1}{k_i!}\cdot n! ti=ki!1⋅n!
将这些式子代入计算期望的式子,得:
a n s = ∑ i = 1 n 1 k i ! ⋅ n ! n ! ans=\frac{\sum_{i=1}^n\frac{1}{k_i!}\cdot n!}{n!} ans=n!∑i=1nki!1⋅n!
提取公因式,得:
a n s = n ! ⋅ ∑ i = 1 n 1 k i ! n ! ans=\frac{n!\cdot \sum_{i=1}^n\frac{1}{k_i!}}{n!} ans=n!n!⋅∑i=1nki!1
化简,得:
a n s = ∑ i = 1 n 1 k i ! ans=\sum_{i=1}^n\frac{1}{k_i!} ans=i=1∑nki!1
这个式子就能计算了,但 k i k_i ki 该如何计算?
一下子就想到了拓扑排序。
可是可能存在环。
考虑使用 Tarjan 算法进行 缩点,再进行拓扑排序。
参考代码
#include<bits/stdc++.h>
using namespace std;
struct bomb{
int x;
int d;
}a[4010];
int n;
bitset<4010>k[4010];
vector<int>e[4010];
set<int>b[4010];
int low[4010];
int dfn[4010];
int vis[4010];
int d[4010];
stack<int>s;
int idx;
vector<vector<int>>ans;
int id[4010];
void Tarjan(int x){
s.push(x);
low[x]=dfn[x]=++idx;
vis[x]=1;
for(int i:e[x]){
if(!dfn[i]){
Tarjan(i);
low[x]=min(low[x],low[i]);
}else if(vis[i]){
low[x]=min(low[x],dfn[i]);
}
}
if(low[x]==dfn[x]){
vector<int>q;
while(s.top()!=x){
auto h=s.top();
s.pop();
vis[h]=0;
q.push_back(h);
id[h]=ans.size();
}
s.pop();
vis[x]=0;
q.push_back(x);
id[x]=ans.size();
ans.push_back(q);
}
}
int main(){
cin.tie(0)->sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].d;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i^j && a[i].x-a[i].d<=a[j].x && a[i].x+a[i].d>=a[j].x){
e[i].push_back(j);
}
}
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
Tarjan(i);
}
}
for(int i=0;i<ans.size();i++){
for(int j:ans[i]){
k[i][j]=1;
for(int k:e[j]){
if(i==id[k])continue;
b[i].emplace(id[k]);
}
}
}
for(int i=0;i<ans.size();i++){
for(int j:b[i]){
d[j]++;
}
}
queue<int>q;
for(int i=0;i<ans.size();i++){
if(!d[i])q.push(i);
}
while(!q.empty()){
auto h=q.front();
q.pop();
for(int i:b[h]){
k[i]|=k[h];
d[i]--;
if(!d[i])q.push(i);
}
}
long double e=0.000000;
for(int i=0;i<ans.size();i++){
e+=(1.000000)/(k[i].count()*1.000000)*ans[i].size();
}
cout<<fixed<<setprecision(4)<<e;
return 0;
}
如有错误,还请指出。
注. 我的洛谷账号即为 lonys,本篇文章同步在洛谷专栏发布。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)