隨機游走問題
隨機游走問題:在一個圖中,有一個棋子。它初始在某一個點上,然后開始移動。當它在\(u\)點時,可以移動到相鄰的\(v\)點(相鄰指兩點之間存在一條邊),它的概率是\(P_{u,v}\),而且\(\sum_{v,v與u相鄰},P_{u,v}=1\)。那么我想知道從一個點出發,第一次到達另一個點平均要走幾步?從一個點出發,走了有限步數,那么它目前處於某一個點的概率是多少?從一個點出發,走的步數趨近於無限,那么它目前處於某一個點的概率是多少?這些都是隨機游走關心的問題。
樹上隨機游走
給定一棵樹,還有\(q\)組詢問,每組詢問給出一個起點\(S\),一個終點\(F\),問從\(S\)出發,期望走幾步恰好到達\(F\)。其中,樹是一個無向圖,如果一個點\(u\)的度數為\(d[u]\),那么任意與\(u\)相鄰的\(v\),\(P_{u,v}=\frac{1}{d[u]}\)。
首先,處理這個問題的時候要知道期望距離的一個性質
性質:\(d(a,b)+d(b,c)=d(a,c)\),這里的\(d(a,b)\)表示從\(a\)出發,恰好走到\(b\)時,期望走的步數。
證明:
假設\(a,c\)不相鄰,在從\(a\)到\(c\)的簡單路徑上存在一點\(b\)(簡單路徑指路徑上的點不重復)
根據樹的性質,兩點之間只能存在一條簡單的路徑
所以想從\(a\)出發到達\(c\),必須經過\(b\)
那么把從\(a\)出發到\(c\)結束的頂點編號記下來,可以看成是
\(v_1,v_2,...,v_k\)
其中\(v_1=a,v_k=c\),\(\exists t,v_t=b\)
而且\(\forall t,v_t=c\to t=k\)
那么我們不妨設滿足\(\exists t,v_t=b\),\(t\)的最小值為\(m\)
\(v_1,v_2,...,v_m\)即從\(a\)出發,第一次到達\(b\)的一條路徑
\(Ex(m-1)=d(a,b)\)
\(v_m,v_{m+1},...,v_k\)即從\(b\)出發,第一次到達\(c\)的一條路徑
\(Ex(k-m)=d(b,c)\)
\(\because v_1,v_2,...,v_m\)序列與\(v_m,v_{m+1},...,v_k\)無關(不考慮\(v_m\)的話)
\(\therefore m-1\)的分布與\(k-m\)的分布無關
\(Ex(k-1)=Ex(m-1)+Ex(k-m)=d(a,b)+d(b,c)\)
\(\because\)按照定義 \(d(a,c)=Ex(k-1)\)
\(\therefore d(a,b)+d(b,c)=d(a,c)\)
這個性質告訴我們,兩個相鄰點之間的期望步數可以看成兩個點之間的邊權。
從\(S\)點到\(F\)點,需要計算\(S,F\)的簡單路徑的邊權和。
給一棵帶邊權的樹,那么可以在\(O(nlogn)\)的預處理后,使用\(LCA\)與連續和的方法在\(O(log\,n)\)的時間內求出\(S,F\)的期望距離。\(LCA\)算法本文暫且不講,讀者可以自行搜索,這里先講邊權如何處理。
邊權的計算方法
本題的情景雖然是樹,但是這里要當成有向圖處理。注意\(d(a,b)\ne d(b,a)\),比如樹的點數為3
邊為\((1,2),(2,3)\),那么\(d(1,2)=1\),\(d(2,1)=3\),是不相等的。
比如我們要求\(d(u,v)\),\(u\)是起點,\(v\)是終點,而且\(u,v\)相鄰
設\(d[u]\)為\(u\)的度數
那么有2種情況
1.\(d[u]=1\),即\(u\)唯一的相鄰點就是\(v\),那么顯然從\(u\)出發走一步就是\(v\)
\(\therefore d(u,v)=1\)
2.\(d[u]>1\),那么從\(u\)點出發有\(d[u]\)種等可能選擇
其中,到\(v\)的可能性是\(\frac{1}{d[u]}\)
到其它點的可能性之和是\(\frac{d[u]-1}{d[u]}\)
從\(u\)節點出發,最終到了\(v\)節點,我們假設在這個路徑中,從\(u\)走到\(v\)之外的相鄰節點恰好\(k\)次。
說明概率\(\frac{d[u]-1}{d[u]}\)的事件連續發生了\(k\)次,之后概率\(\frac{1}{d[u]}\)的事件發生了一次,
記為\(P(sub_k)=\frac{1}{d[u]}(\frac{d[u]-1}{d[u]})^k\)
然后在事件\(sub_k\)下,從\(u\)走到\(v\)之外的相鄰節點恰好\(k\)次,每次選擇哪一個節點都是隨機的,
每一次的條件概率為\(\frac{1}{d[u]-1}\)。
那么設某一次,從\(u\)走到\(v\)之外的相鄰節點\(y\),需要1步,然后再從\(y\)返回\(u\),又需要期望\(d(y,u)\)步
其中,\(y\)完全是隨機的。
那么這一次,從\(u\)走到\(v\)之外的相鄰節點\(y\),再回到\(u\),需要的期望步數為
\(\sum_{u,y相鄰,y\ne v}\frac{1}{d[u]-1}(1+d(y,u))\)。
這種事件連續發生了\(k\)次,所以總的期望步數為\(k\sum_{u,y相鄰,y\ne v}\frac{1}{d[u]-1}(1+d(y,u))\)
由於最后一步一定是從 \(u\) 走到 \(v\),再加\(1\)
\(\therefore d(u,v)=1+\sum_{k=0}^{\infty} P(sub_k)k\sum_{u,y相鄰,y\ne v}\frac{1}{d[u]-1}(1+d(y,u))\)
\(=1+(\sum_{k=0}^{\infty} \frac{kP(sub_k)}{d[u]-1})\times \sum_{u,y相鄰,y\ne v}(1+d(y,u))\)
其中\((\sum_{k=0}^{\infty} \frac{kP(sub_k)}{d[u]-1})=\frac{1}{d[u]}\frac{1}{d[u]-1}\sum_{k=0}^{\infty} k(\frac{d[u]-1}{d[u]})^k\)
其中,\(\sum_{k=0}^{\infty} k(\frac{d[u]-1}{d[u]})^k\)應用錯位相減法,
求得\(\sum_{k=0}^{\infty} k(\frac{d[u]-1}{d[u]})^k=(d[u]-1)(d[u])\)
\(\therefore (\sum_{k=0}^{\infty} \frac{kP(sub_k)}{d[u]-1})=1\)
\(\therefore d(u,v)=1+\sum_{u,y相鄰,y\ne v}(1+d(y,u))\)
綜上
\(d(u,v)=1+\sum_{u,y相鄰,y\ne v}(1+d(y,u))\)
如何編程計算?
對於一棵樹,我們可以令點\(1\)為根,形成一棵有根樹(這才有深度和父子關系)。設\(S,F\)的最近公共祖先為\(M\),從\(S\)到\(F\)的簡單路徑,可以看成一條從\(S\)到\(M\)的上行路徑(上行即深度越來越淺,往樹根方向走),與一條從\(M\)到\(F\)的下行路徑(上行即深度越來越深,往樹根反方向走)。
設\(now\)為某個節點的編號,\(fa[now]\) 為\(now\)的父節點,設\(up[now]\)為從\(d(now,fa[now])\),設\(down[now]=d(fa[now],now)\)。
\(up[now]\)的計算
\(now\ne 1\)
1.\(d[now]>1\),即\(now\)不是葉節點
根據\(d(u,v)=1+\sum_{u,y相鄰,y\ne v}(1+d(y,u))\)
\(up[now]=d(now,fa[now])=1+\sum_{now,y相鄰,y\ne fa[now]}(1+d(y,now))\)
由於滿足\(now,y相鄰,y\ne fa[now]\)的\(y\)都是\(now\)的子節點
上式\(=1+\sum_{fa[y]=now}(1+up[y])\)
2.\(d[now]=1\),即\(now\)是葉節點
那么根據\(d(u,v)=1\)
\(up[now]=d(now,fa[now])=1\)
綜上,
\(up[now]=1+\sum_{fa[y]=now}(1+up[y])\)
這是一個遞歸的定義式,可以遞歸求解。
我們不妨設\(sum\_up[now]=\sum_{fa[y]=now}(1+up[y])\)
\(up[now]=1+sum\_up[now],now\ne 1\)
\(down[now]\)的計算
\(now\ne 1\)
1.\(fa[now]\ne 1\),即\(now\)不是樹根的兒子
根據\(d(u,v)=1+\sum_{u,y相鄰,y\ne v}(1+d(y,u))\)
\(down[now]=d(fa[now],now)=1+\sum_{fa[now],y相鄰,y\ne now}(1+d(y,fa[now]))\)
\(=1+\sum_{fa[now],y相鄰}(1+d(y,fa[now]))-d(now,fa[now])-1\)
\(=\sum_{fa[now],y相鄰,y\ne fa[fa[now]]}(1+d(y,fa[now]))+1+d(fa[fa[now]],fa[now])-d(now,fa[now])\)
由於滿足\(fa[now],y相鄰,y\ne fa[fa[now]]\)的\(y\)都是\(fa[now]\)的兒子
\(down[now]=\sum_{fa[y]=fa[now]}(1+d(y,fa[now]))+d(fa[fa[now]],fa[now])-d(now,fa[now])+1\)
\(=\sum_{fa[y]=fa[now]}(1+up[y])+down[fa[now]]-up[fa[now]]+1\)
\(=sum\_up[fa[now]]+down[fa[now]]-up[fa[now]]+1\)
2.\(fa[now]=1\),即\(now\)是樹根的兒子
\(d(u,v)=1+\sum_{u,y相鄰,y\ne v}(1+d(y,u))\)
\(down[now]=d(fa[now],now)=1+\sum_{fa[now],y相鄰,y\ne now}(1+d(y,fa[now]))\)
\(=1+\sum_{fa[now],y相鄰}(1+d(y,fa[now]))-d(now,fa[now])-1\)
\(\because fa[now]=1\),是根節點
\(\therefore 滿足fa[now],y相鄰\)的\(y\)都是\(fa[now]\)的兒子
\(\therefore down[now]=1+\sum_{fa[y]=fa[now]}(1+d(y,fa[now]))-d(now,fa[now])-1\)
\(=\sum_{fa[y]=fa[now]}(1+up[y])-up[now]\)
\(=sum\_up[fa[now]]-up[now]\)
綜上,
最后再把這兩個結論放在一起
\(up[now]=1+sum\_up[now],now\ne 1\)
其中\(sum\_up[now]=\sum_{fa[y]=now}(1+up[y])\)
我們先利用一個\(dfs\)計算\(up[now]\)和\(sum\_up[now]\),再利用另一個\(dfs\)計算\(down[now]\)
這樣我們就把任意兩個相鄰點之間的期望步數算出來了,由遞歸定義可知,它們都是正整數,不用考慮分數的情況。
求任意兩點的期望步數
現在這就是一個圖論問題了,因為邊權都已經求出來了(注意邊權是分向樹根方向和向樹葉方向的),現在需要求任意兩點之間的距離。提示:這個需要用到\(LCA\)
最后貼出代碼:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
int read()
{
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while( isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
ll mod=1000000007;
const int maxn=1e5+10;
ll d[maxn];//度數
struct edge
{
int y,next;
}map[maxn*2];
int head[maxn];
ll up[maxn],down[maxn];
ll sum_up[maxn];
void add(int num,int x,int y)
{
map[num].y=y;
map[num].next=head[x];
head[x]=num;
d[x]++;
}
void dfs_up(int now,int fa)
{
ll ans=0;
for(int k=head[now];k!=-1;k=map[k].next)
{
int y=map[k].y;
if(y==fa)continue;
dfs_up(y,now);
ans=(ans+1+up[y])%mod;
}
sum_up[now]=ans;
if(d[now]>1)//不是葉節點
{
ans=ans;//數學公式
}
ans=(ans+1)%mod;
up[now]=ans;
if(now==1)
{
up[now]=0;
}
}
void dfs_down(int now,int fa)//從fa走到now
{
if(now==1)
{
down[now]=1;
}
else if(fa==1)
{
down[now]=(sum_up[fa]-up[now]+mod)%mod;
}
else
{
down[now]=(sum_up[fa]-up[now]+down[fa]+1+mod)%mod;
}
for(int k=head[now];k!=-1;k=map[k].next)
{
int y=map[k].y;
if(y==fa)continue;
dfs_down(y,now);
}
}
ll pre_up[maxn],pre_down[maxn];
int deep[maxn],ance[maxn][18];
void dfs(int now,int fa)
{
for(int k=head[now];k!=-1;k=map[k].next)
{
int y=map[k].y;
if(y==fa)continue;
ance[y][0]=now;
deep[y]=deep[now]+1;
pre_up[y]=(up[y]+pre_up[now])%mod;
pre_down[y]=(down[y]+pre_down[now])%mod;
for(int temp=2,i=0;deep[y]>temp;i++,temp*=2)//temp=2^(i+1)
{
ance[y][i+1]=ance[ance[y][i]][i];
}
dfs(y,now);
}
}
int LCA(int x,int y)
{
if(deep[x]>deep[y])swap(x,y);
//x一定比y淺(或同深度)
int del=deep[y]-deep[x];
for(int i=0;del>0;i++,del/=2)
{
if(del%2==1)
{
y=ance[y][i];
}
}
//x,y同深度
int len=0;
int dep=deep[x];
while(dep)
{
dep/=2;
len++;
}
for(int i=len;i>0;i--)
{
if(ance[x][i]==ance[y][i]&&ance[x][i-1]!=ance[y][i-1])
{
x=ance[x][i-1];
y=ance[y][i-1];
}
}
if(x!=y)return ance[x][0];
else return x;
}
int main()
{
memset(head,-1,sizeof(head));
int n=read(),q=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read();
add(i*2-1,x,y);
add(i*2,y,x);
}
dfs_up(1,-1);
dfs_down(1,-1);
deep[1]=1;
dfs(1,-1);
while(q--)
{
int x=read(),y=read();
int lca=LCA(x,y);
ll ans=(pre_up[x]-pre_up[lca]+pre_down[y]-pre_down[lca]+2*mod)%mod;
printf("%lld\n",ans);
}
}
無向圖的隨機游走問題
設一個無向圖,有一個棋子從某點出發,隨機游走,問它在運動幾步后處於某個點的概率
設圖的聯通性矩陣為\(A\)
\(A_{ij}\)表示第\(i,j\)兩點是否相鄰
比如這個矩陣
即下圖
還有度數矩陣\(B\)
它是一個對角矩陣
\(B_{ii}=d[i]\)
該圖的度數矩陣\(B\)為
設當前的狀態向量\(p\)
\(p_{i1}=P(i)\),即在\(i\)點處的概率
那么走到下一步后
\(p^{'}=AB^{-1}p\)
這一步是很抽象的,希望讀者自己推理
由這一點得出的另一個非常有意思的結論:
無論初始狀態\(p\)如何
當走的步數趨近於無窮大時,\(p\)收斂於\(p_{i1}=\frac{d[i]}{\sum_{i=1}^{n}d[i]}\)
即一個棋子在隨機游走了無窮步后處於一個點的概率和這個點的度數成正比