[Contest on 2021.10.7] 已經起不了標題了...
變異大老鼠
題目描述
你的轄區有一隻家喻戶曉的變異大老鼠,他生性險惡,作惡無數。你的轄區可以表示為一張 \(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}\):令 \(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\)~