绿豆蛙的归宿

匿名 (未验证) 提交于 2019-12-02 23:32:01

绿豆蛙的归宿

给定点数为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; } 

看完这三种思路,必然感慨良多,期望确实没有什么固定的套路,任何创新的拆分与不同的角度,都会带来不同的解决方案。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!