给定点数为n的dag以及边权,询问起点到终点经过的边权的期望值,\(N<=100000\)。
法一:
期望题,考虑倒推,设\(f[x]\)表示从点x到终点的路径期望长度,设y是一个与x相连的出点,设\(out[x]\)为x的出点的个数,设\(s[x][y]\)与x,y相连的边权,不难得知
\[f[x]=\frac{f[y]+dis[x][y]}{out[x]}\]
注意到只有当x算完后,才能继续向后算,于是考虑建反边,用拓扑排序的方法转移方程。
另外注意到深度优先搜索的特点,一定是该点的出点遍历完再转移该点,所以此处也可以用dfs实现。
参考代码:
拓扑排序
#include <iostream> #include <cstdio> #define il inline #define ri register #define lb long double using namespace std; struct point{ point*next;int to,len; }*head[100001],*pt; lb dp[100001]; int team[100001],in[100001],dag[100001]; il void link(int,int,int),read(int&), bfs(int); int main(){ int n,m,i,j,k; read(n),read(m); while(m--)read(i),read(j),read(k), link(j,i,k),++in[i]; for(i=1;i<=n;++i)dag[i]=in[i]; bfs(n),printf("%.2Lf",dp[1]); return 0; } il void bfs(int s){ int h(0),t(1);team[1]=s; while(h<t){++h; for(pt=head[team[h]];pt!=NULL;pt=pt->next){ dp[pt->to]+=(dp[team[h]]+pt->len)/in[pt->to]; --dag[pt->to];if(!dag[pt->to])team[++t]=pt->to; } } } il void read(int&x){ x&=0;ri char c;while(c=getchar(),c<'0'||c>'9'); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar(); } il void link(int x,int y,int len){ pt=new point,pt->to=y,pt->len=len; pt->next=head[x],head[x]=pt; }
dfs版
#include <iostream> #include <cstdio> #define il inline #define ri register #define db double #define exact 0.0001 using namespace std; struct point{ point*next;int to,len; }*head[100001],*pt; int out[100001];db dp[100001]; il db search(int); il void read(int&),link(int,int,int); int main(){ int n,m,i,j,k; read(n),read(m); while(m--)read(i),read(j),read(k), link(i,j,k),++out[i]; search(1),printf("%.2lf",dp[1]); return 0; } il db search(int x){ if(dp[x]>exact)return dp[x]; for(point *i(head[x]);i!=NULL;i=i->next) dp[x]+=(search(i->to)+i->len)/out[x]; return dp[x]; } il void link(int x,int y,int len){ pt=new point,pt->to=y,pt->len=len; pt->next=head[x],head[x]=pt; } il void read(int &x){ x&=0;ri char c;while(c=getchar(),c<'0'||c>'9'); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar(); }
法二:
既然倒推能够实现,那么考虑顺推,设\(out[x]\)表示x的出度,\(e[x]\)为从起点到该点的路径长度的期望,\(p[x]\)表示从起点到该点的概率,所以不难有,设y与x相连且为其出点,\(s[x][y]\)为x间y的边权,不难有
\[e[x]=e[y]/out[y]+s[x][y]p[y]/out[y],p[x]=p[y]/out[y]\]
顺着拓扑排序,同时维护概率和期望,当然你也可以倒过来dfs,终点点的编号对应的期望即我们的答案。
参考代码:
#include <iostream> #include <cstdio> #include <queue> #define il inline #define ri register using namespace std; struct point{ point*next;int to,len; }*head[100001],*pt; double p[100001],e[100001]; int out[100001],in[100001]; il void link(int,int,int),read(int&); int main(){ int n,m,i,j,k; scanf("%d%d",&n,&m); while(m--) read(i),read(j),read(k), ++out[i],++in[j],link(i,j,k); queue<int>t;t.push(1),p[1]=1; while(!t.empty()){ i=t.front(),t.pop(); for(pt=head[i];pt!=NULL;pt=pt->next){ p[pt->to]+=p[i]/out[i]; e[pt->to]+=(e[i]+pt->len*p[i])/out[i]; --in[pt->to];if(!in[pt->to])t.push(pt->to); } }printf("%.2lf",e[n]); return 0; } il void read(int &x){ x&=0;ri char c;while(c=getchar(),c<'0'||c>'9'); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar(); } il void link(int x,int y,int len){ pt=new point,pt->to=y,pt->len=len; pt->next=head[x],head[x]=pt; }
法三:
只对于一条边考虑,不难得知它的概率很好维护,法二已经介绍了方法,而我们完全可以只维护概率,撇开期望做,同样dagdp,也就是拓扑排序,不妨让变量与法二相同,不难有
\[ans+=\frac{p[y]}{out[y]}s[x][y],p[x]+=\frac{p[y]}{out[y]}\]
以此维护累加答案即可,实际上仔细理解一下你会发现,加上的式子即法二期望递推方程的第二项,我们只是撇开了期望,其实你也可以这样理解,也就是所谓的法二中的期望也就是把法三中的\(\frac{p[y]}{out[y]}\),给存到每个点再累加到终点。
参考代码:
#include <iostream> #include <cstdio> #include <queue> #define il inline #define ri register using namespace std; struct point{ point*next;int to,len; }*head[100001],*pt; double p[100001],ans; int out[100001],in[100001]; il void link(int,int,int),read(int&); int main(){ int n,m,i,j,k; scanf("%d%d",&n,&m); while(m--) read(i),read(j),read(k), ++out[i],++in[j],link(i,j,k); queue<int>t;t.push(1),p[1]=1; while(!t.empty()){ i=t.front(),t.pop(); for(pt=head[i];pt!=NULL;pt=pt->next){ p[pt->to]+=p[i]/out[i]; ans+=pt->len*p[i]/out[i]; --in[pt->to];if(!in[pt->to])t.push(pt->to); } }printf("%.2lf",ans); return 0; } il void read(int &x){ x&=0;ri char c;while(c=getchar(),c<'0'||c>'9'); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar(); } il void link(int x,int y,int len){ pt=new point,pt->to=y,pt->len=len; pt->next=head[x],head[x]=pt; }
看完这三种思路,必然感慨良多,期望确实没有什么固定的套路,任何创新的拆分与不同的角度,都会带来不同的解决方案。