变异大老鼠
题目描述
你的辖区有一只家喻户晓的变异大老鼠,他生性险恶,作恶无数。你的辖区可以表示为一张 \(n\) 个点的带权无向图。
你仔细研究大老鼠的案底后得出了以下结论:
-
首先,大老鼠反侦察能力极强,他知道他走过的地方会被猫警察发现,于是他从不走回头路。
-
其次,他深知多走一秒钟路就多一秒钟被发现的危险,因此,他提前摸清了你所属辖区的道路状况,并且只会走 \(1\) 号结点到其他结点的最短路。幸运的是,你管辖的辖区,\(1\) 号结点到其他每个结点的最短路 只有一条。
-
再次,他为了尽可能地逃离案发现场,他必须不停地走下去。也就是说,只要有相邻的结点满足以上两个要求(只走最短路,不走回头路),他就一定会移动。如果有多个结点满足,他会等概率随机地选择一个结点走去。如果没有结点满足要求,那么他就会选择逃窜到垃圾堆之中,在垃圾堆里抓鼠的难度可想而知,这也就意味着你的行动失败了。
你决定在一些结点上布置 \(k\) 个猫警察,当大老鼠抵达这个结点时,这个结点上的警察便会逮捕大老鼠。不过大老鼠有一定的概率摆脱逮捕,当他摆脱逮捕后,他会像没事发生一样按上文的方式继续行动,直至遁入垃圾堆或在之后的某个地方被逮捕。
你作为一个学过 \(\rm OI\) 的警察猫局长,已经知道了你的辖区内每个点设置不同数量的猫警察成功抓捕大老鼠的概率 \(p\)。你需要找出最优的安排猫警察埋伏的方案,从而使得将大老鼠逮捕归案的概率最大。
\(1 ≤n, k≤300,1 ≤m ≤3 ×10^4,p_{i,j} ≤p_{i,j+1}\)。
解法
由于 "\(1\) 号结点到其他每个结点的最短路只有一条",所以实际上老鼠的行动路径可以被表示成一棵树。先开始的想法是计算出老鼠到达每个点的概率,再用一个 \(\mathcal O(nk^2)\) 的 \(\mathtt{dp}\) 来计算最大概率。
但是实际上老鼠到达每个点的概率和逮捕的概率有关,不能这么简单计算。从上往下计算概率似乎有些麻烦,因为这还和在祖先上放置的警察集合有关。我们不妨从子树往上 \(\mathtt{dp}\):令 \(dp_{i,j}\) 为根为 \(i\) 的子树,放置 \(j\) 个警察的最大逮捕概率。还是 \(\mathcal O(nk^2)\) 的。
代码
#include <cmath>
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 305;
const int inf = 0x3f3f3f3f;
int n,m,all,w[maxn][maxn];
int e[maxn][maxn];
double p[maxn][maxn];
double dp[maxn][maxn];
vector <int> E[maxn];
void Floyd() {
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
w[i][j]=min(w[i][j],w[i][k]+w[k][j]);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) {
if(i==j) continue;
if(w[1][j]==w[1][i]+e[i][j])
E[i].push_back(j);
}
}
void dfs(int u) {
double tmp[maxn]={};
int son=0;
for(auto v:E[u]) {
dfs(v); ++son;
for(int i=all;i>=0;--i)
for(int j=0;i+j<=all;++j)
tmp[i+j] = max(
tmp[i+j],
tmp[i]+dp[v][j]
);
}
if(!son) son=1;
for(int i=0;i<=all;++i)
for(int j=0;i+j<=all;++j)
dp[u][i+j] = max(
dp[u][i+j],
tmp[j]/son*(1-p[u][i])+p[u][i]
);
}
int main() {
n=read(9),m=read(9),all=read(9);
int x,y;
memset(w,0x3f,sizeof w);
memset(e,0x3f,sizeof e);
for(int i=1;i<=n;++i)
w[i][i]=e[i][i]=0;
for(int i=1;i<=m;++i)
x=read(9),y=read(9),
w[x][y]=w[y][x]=min(read(9),w[x][y]),
e[x][y]=e[y][x]=w[x][y];
Floyd();
for(int i=1;i<=n;++i)
for(int j=1;j<=all;++j)
scanf("%lf",&p[i][j]);
dfs(1);
printf("%.6f\n",dp[1][all]);
return 0;
}
朝鲜时蔬
因为太懒了,所以丢一份 \(\rm Blog\)~