同样是高斯消元,我写的版本就受到了歧视
我怎么又犯把 $j$ 打成 $i$ 这种 $sb$ 错误
题意
一张无向图,两个人分别从 $s_1$ 号点和 $s2$ 号点开始,每轮两人都会同时进行一次以下操作:有 $p_i$ 的概率留在原地,剩下的 $1-p_i$ 的概率等概率随机选择一条出边走到连向的点。问两人在每个点相遇的概率(边上不相遇)。
$nle 20$
题解
首先得知道,这种题的概率就是期望,因为在点 $i$ 相遇($1le ile n$)的期望和是 $1$,总概率也是 $1$。
期望和概率的一个重要差别就是期望可以不在 $[0,1]$ 范围内,之后转化的时候记住了。
这种题都有一种特性,就是感觉可以拓扑排序 $dp$。
但拓扑排序只适用于 $DAG$(有向无环图),对于有环图(包括无向图),没法确定 $dp$ 转移顺序。
好了,现在我们降低自己的智商,回到小学状态,想想在小学时你怎么做这种难题。
如果你学过小学奥数(没学过也无所谓吧),应该能想到列方程。
那方程是什么?就是我们的 $dp$ 式子,因为我们只是不确定转移顺序,但式子肯定是对的。
众所周知,方程是互相之间都有约束性的,解必须同时满足所有方程,所以可以解决没法确定转移顺序的情况。
所以这是高斯消元入门题。
由于 $nle 20$,时间空间都随便用,先不用管会不会爆。
考虑正常 $dp$,设 $f(i,j)$ 表示两人分别在点 $i,j$ 的概率。
那 $f(i,j)$ 可以从它自己及所有直接相连的点转移过来。
我们再枚举哪个点转 $x$ 移到点 $i$,哪个点 $y$ 转移到点 $j$。
分类讨论,
当 $i=x,space j=y$ 时,从 $f(x,y)$ 转移到 $f(i,j)$ 的概率是 $p_x imes p_y$;
当 $i=x,space j≠y$ 时,从 $f(x,y)$ 转移到 $f(i,j)$ 的概率是 $p_x imes (1-p_y)/du_y$;
当 $i≠x,space j=y$ 时,从 $f(x,y)$ 转移到 $f(i,j)$ 的概率是 $(1-p_x) imes p_y/du_x$;
当 $i≠x,space j≠y$ 时,从 $f(x,y)$ 转移到 $f(i,j)$ 的概率是 $(1-p_x) imes (1-p_y)/du_x/du_y$;
其中 $du_x$ 表示点 $x$ 的度(就是有多少个点与它直接相连),注意点 $x$ 自己不算。
我们把所有的 $f(i,j)space |space 1le ile n,space 1le jle n$ 都看作一个未知数,这样方程组共有 $n^2$ 个未知数,并且我们知道一个未知数表示的二元组(就是两人的位置)到另一个未知数表示的二元组的概率,也就是转移系数(其实就是系数,比如状态 $A$ 有 $k$ 的概率转移到 $B$,方程中就是 $B=kA$)是多少,这样就可以把所有转移系数列成矩阵,解方程组了。
一行方程大概长这样(区分了一下 $i,x$ 和 $j,y$) $$f(i,j) space =space f(i,j) imes ... + f(i,y) imes ... + f(x,j) imes ... + f(x,y) imes ...$$
把等号左边的 $f(i,j)$ 移到右边得到 $$0 space =space f(i,j) imes ... + f(i,y) imes ... + f(x,j) imes ... + f(x,y) imes ... - f(i,j)$$
也就是对于转移到的状态 $f(i,j)$,它对应的那行方程 等号右边的 $f(i,j)$ 那项的系数先减 $1$。
这个移项处理是高斯消元的终点,把未知数统一移到一边,常数统一移到另一边,省得再判等号两边的未知数。
起点作为被转移项时,其方程的等号左边是 $-1$,因为两人一开始就都在起点,所以已经期望有 $1$ 次到达这种状态,等号右边(所有未知数那边)就加了 $1$,移项到另一边就变成减 $1$。
再注意一点,两人终止的状态不再往其它状态转移,即两人的转移来源点 $x$ 和 $y$ 不能相等。
1 #include<bits/stdc++.h> 2 #define rep(i,x,y) for(int i=x;i<=y;++i) 3 #define dwn(i,x,y) for(int i=x;i>=y;--i) 4 #define rep_e(i,u) for(int i=hd[u];i;i=e[i].nxt) 5 #define ll long long 6 #define N 22 7 #define eps 1e-7 8 using namespace std; 9 inline int read(){ 10 int x=0; bool f=1; char c=getchar(); 11 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 12 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 13 if(f) return x; 14 return 0-x; 15 } 16 int n,m,a,b; 17 double p[N],dp[N*N][N*N],ans[N*N]; 18 struct edge{int v,nxt;}e[(N*N)<<1]; 19 int hd[N],cnt,du[N]; 20 inline void add(int u,int v){e[++cnt]=(edge){v,hd[u]}, hd[u]=cnt;} 21 inline int getCnt(int x,int y){return (x-1)*n+y;} 22 bool Gauss(int n){ 23 rep(i,1,n){ 24 int r; 25 //cout<<"wow:"<<i<<endl; 26 for(r=i; r<=n && fabs(dp[i][r])<eps; ++r); 27 28 if(fabs(dp[i][r])<eps) return 0; 29 /* 30 rep(i,1,n){ 31 rep(j,1,n+1) cout<<dp[i][j]<<' '; 32 cout<<endl; 33 }*/ 34 if(i^r) swap(dp[i],dp[r]); 35 /* 36 rep(i,1,n){ 37 rep(j,1,n+1) cout<<dp[i][j]<<' '; 38 cout<<endl; 39 }*/ 40 double div=dp[i][i]; 41 //cout<<div<<endl; 42 rep(j,i,n+1) dp[i][j]/=div; 43 rep(j,i+1,n){ 44 div=dp[j][i]; 45 rep(k,i,n+1) 46 dp[j][k]-=dp[i][k]*div; 47 } 48 } 49 /* 50 rep(i,1,n){ 51 rep(j,1,n+1) cout<<dp[i][j]<<' '; 52 cout<<endl; 53 }*/ 54 ans[n]=dp[n][n+1]; 55 dwn(i,n-1,1){ 56 ans[i]=dp[i][n+1]; 57 rep(j,i+1,n) ans[i]-=ans[j]*dp[i][j]; 58 } 59 /* 60 rep(i,1,n){ 61 ans[i]=dp[fd][n+1]/dp[fd][fd]; 62 }*/ 63 return 1; 64 } 65 int main(){ 66 n=read(),m=read(),a=read(),b=read(); 67 int u,v,all=n*n; 68 rep(i,1,n) add(i,i); 69 rep(i,1,m) 70 u=read(),v=read(), 71 ++du[u],++du[v], 72 add(u,v),add(v,u); 73 rep(i,1,n) 74 cin>>p[i]; 75 dp[getCnt(a,b)][all+1]=-1; 76 rep(i,1,n) 77 rep(j,1,n){ 78 int cnt1=getCnt(i,j); 79 --dp[cnt1][cnt1]; //i=j时自己不能转移到自己,所以自己给自己的转移的期望次数-1 80 rep_e(ii,i) 81 rep_e(jj,j){ 82 int x=e[ii].v, y=e[jj].v; 83 //cout<<"try:"<<i<<' '<<j<<' '<<x<<' '<<y<<endl; 84 if(x^y){ 85 int cnt2=getCnt(x,y); 86 if(i==x && j==y) dp[cnt1][cnt2]+=p[i]*p[j]; 87 else if(i==x) dp[cnt1][cnt2]+=p[i]*(1-p[y])/du[y]; 88 else if(j==y) dp[cnt1][cnt2]+=(1-p[x])*p[j]/du[x]; 89 else dp[cnt1][cnt2]+=(1-p[x])*(1-p[y])/du[x]/du[y]; 90 //cout<<i<<' '<<j<<' '<<x<<' '<<y<<' '<<cnt1<<' '<<cnt2<<' '<<p[x]<<' '<<p[y]<<' '<<du[x]<<' '<<du[y]<<' '<<dp[cnt1][cnt2]<<endl; 91 } 92 } 93 } 94 95 if(!Gauss(all)){printf("I AK IOI! "); return -1;} 96 rep(i,1,n) printf("%.6lf ",ans[getCnt(i,i)]); 97 return 0; 98 } 99 /* 100 3 2 1 3 101 1 2 102 2 3 103 0.5 104 0.5 105 0.5 106 显然,ans[1]和ans[3]应该相等,否则就是程序写错了 107 */
怎么网上那么多人写的代码都一个样(包括高斯消元部分),这么不求上进的吗?
我写的高斯消元跟他们不一样,一开始结果不对,我还以为我的高斯消元是错的。
后来调对了,才意识到只是被鄙视了而已,因为我跟其他人写的都不一样。
然后呢?