给定点数为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;
}
看完这三种思路,必然感慨良多,期望确实没有什么固定的套路,任何创新的拆分与不同的角度,都会带来不同的解决方案。