1. 程式人生 > >矩陣快速冪優化dp -E. A Trance of Nightfall(CodeForces989)

矩陣快速冪優化dp -E. A Trance of Nightfall(CodeForces989)

傳送門

Analysis

好題啊,不會做的都是好題,emmm dzyo說這道題不是一眼dp嗎。。。。。(好吧好吧,那就假設我們知道這道題可以dp搞了,反正我不知道)

由問題:“求連續移動mi步,最後到達ti的最大概率是多少” 可知 我們可以定義狀態Ai,u,vA_{i,u,v}表示從 u 走 i 步到達 v 的概率是多少 最後我們的目標就是max(Ami,x,ti)max (A_{mi,x,ti})( x 是我們一開始選擇的起點) 顯然我們的轉移就是 Ai+1,u,k=Σ(Ai,u,vA1,v,k)A_{i+1,u,k}=\Sigma(A_{i,u,v}*A_{1,v,k})

然後我們驚奇地發現這不就是矩陣乘法嗎???(這個轉移形式可以有) 但如果每次詢問都乘 mi 次,時間肯定耗不起 怎麼辦呢? 常見套路 (倍增思想): 預處理走 1、2、4、8、16……步的矩陣。 這樣的話,詢問時就是用一個 1n1*n 的矩陣與 log 個nnn*n 的矩陣相乘。

現在來考慮怎麼計算A 如果在x 有cnt條直線可供選擇,y所在的直線有 num 個點,那麼從 x 到y的概率是1cntnum\frac{1}{cnt*num} 那我們就先處理出有多少條直線經過點i,得到cnt[i]cnt[i]

再處理同一條直線上任意兩點互相到達的概率1num\frac{1}{num}‘然後就可以計算任意兩點的概率了(選擇這條直線的概率*在這條直線上走到這個點的概率) 是不是就完了呀?

不不不,當然不是 我們還沒有考慮出發點呢 顯然出發點的選擇只有兩種情況 1.在兩個景點構成的直線上取一個點 2.選擇景點 那我們分開考慮,再取max即可 注意對於情況1,我們先走mi-1步走到一個景點上去

Code

#include<bits/stdc++.h>
using namespace std;
const int N=300;
int n,x[N],y[N],m;
int cnt[
N]; bool vis[N]; double g[N],tmp[N],f[20][N][N]; vector<int> G[N][N]; vector<pair<int,int> > line; bool judge(int u,int v,int i){//判斷i這個點是否在u,v構成的直線上 return (x[i]-x[v])*(y[v]-y[u])==(x[v]-x[u])*(y[i]-y[v]); } int main(){ scanf("%d",&n); int i,j; for(i=1;i<=n;++i)//讀入每一個點 scanf("%d%d",&x[i],&y[i]); //預處理出每一個點會被多少個直線經過 for(i=1;i<=n;++i){ memset(vis,0,sizeof(vis));//如果vis標記為1,則說明當前這個點和i屬於同一直線 for(j=1;j<=n;++j){ if(vis[j]) continue; if(i==j) continue; ++cnt[i]; for(int u=1;u<=n;++u) if(judge(i,j,u)) G[i][j].push_back(u),vis[u]=1; line.push_back(make_pair(G[i][j][0],G[i][j][1])); } } sort(line.begin(),line.end()); line.erase(unique(line.begin(),line.end()),line.end());//重邊 for(int i=0;i<line.size();++i){//計算每一條直線上任意兩點互相到達的概率 vector<int> &vec=G[line[i].first][line[i].second];//寫成引用(隨手卡常(zxy%%),vector的複製可不是O(1)的啊 for(int u=0;u<vec.size();++u) for(int v=0;v<vec.size();++v) f[0][vec[u]][vec[v]]+=1.0/(1.0*vec.size());//加等於 } for(int i=1;i<=n;++i) //計算走到這條直線的概率 for(int j=1;j<=n;++j) f[0][i][j]/=cnt[i]; for(int i=1;i<=15;++i)//預處理倍增 { for(int u=1;u<=n;++u) for(int v=1;v<=n;++v) for(int k=1;k<=n;++k) f[i][u][v]+=f[i-1][u][k]*f[i-1][k][v]; } scanf("%d",&m); for(int t=1;t<=m;++t){ int goal,stp; scanf("%d%d",&goal,&stp); stp--; memset(g,0,sizeof(g)); g[goal]=1;//g[i]表示從i走到goal的概率 for(int i=0;(1<<i)<=stp;++i){//處理每個點往外走stp-1步走到goal的概率 if((1<<i)&stp){ memset(tmp,0,sizeof(tmp)); for(int u=1;u<=n;++u) if(g[u]>1e-6) for(int v=1;v<=n;++v) tmp[v]+=f[i][v][u]*g[u]; memcpy(g,tmp,sizeof(tmp)); } } double ans=-1; double hh=0; for(int i=0;i<line.size();++i){//再計算從每條直線走到其上點的概率 hh=0; vector<int> &vec=G[line[i].first][line[i].second]; for(int j=0;j<vec.size();++j) hh+=g[vec[j]]; hh/=1.0*vec.size(); ans=max(ans,hh); } memset(tmp,0,sizeof(tmp)); for(int u=1;u<=n;++u) if(g[u]>1e-6)//計算每個點往外走stp步走到goal的概率 for(int v=1;v<=n;++v) tmp[v]+=f[0][v][u]*g[u]; for(int i=1;i<=n;++i) ans=max(ans,tmp[i]); printf("%lf\n",ans); } return 0; }