bzoj3470 Freda's Walk (期望概率DP)

51 篇文章 1 订阅
13 篇文章 0 订阅

bzoj3470 Freda’s Walk

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3470

题意:
有向无环图,求从点0出发,走到一个出度为0的点停止,点u出发的所有边的权值之和为s,从u到v的边的权值为w,走向v的概率就是 w/s。可以任意删一条边(也可不删边),求最长期望路径长度。

因为是权限题,贴一下输入输出
Input

第一行两个正整数 n、m。
接下来m行每行三个整数u、v、w,表示从u到v有一条无向边,边权w。

Output

输出路程长度的最大期望值,四舍五入保留六位小数。

Sample Input

4 5

0 1 2

0 2 1

0 3 3

1 3 1

2 3 4
Sample Output

2.000000

数据范围
对于 100% 的数据,2<=n<=10000,1<=m<=100000,0<=u,v< n

题解:
这题我最开始想的就是dp[u][0/1] 表示按拓扑序其之后的边 没删过/删过 的,从这个点到停止的期望路径长度。每个dp[u][1]只能取一个dp[v][1]转移过来。答案即在max(f[1][0],f[1][1])
但是,这样是错的。
这里有一个反例:
这里写图片描述
如果删3—>4这条边 ,当计算f[1]时,对点2,点3,都是 ‘删过’,不能说只取一个f[v][1]。
现在我们不要f[i][0/1]了,定义f[i]为点到停止的期望路径长度(相当于原来的f[i][0])。
为了避免上面的情况,之间先计算出从1点到每个点的概率p[i],则不删边时这个点对于答案的贡献就是p[i]*f[i]。
枚举每个点所有的出边,计算出删除这个点某条出边所得的最大的新期望 f[i]’。对于答案增加的贡献就是 ( f[i]’-f[i] ) * p[i]

所以 ans=f[1]+max( ( f[i]’-f[i] ) * p[i] )

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define LL long long
using namespace std;
const int N=10005;
const int M=100005;
int n,m,head[N],to[M],nxt[M],out[N],in[N],w[M],num=0;
int U[M],V[M],W[M];
double f[N][2],p[N],mx;
bool vis[N];
void build(int u,int v,int ww)
{
    num++;
    to[num]=v;
    nxt[num]=head[u];
    w[num]=ww;
    head[u]=num;
}
void getp()
{
    queue<int> Q;
    for(int i=1;i<=n;i++)if(!in[i]) Q.push(i);
    while(!Q.empty())
    {
        int u=Q.front(); Q.pop();
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            p[v]+=p[u]*(double)w[i]/out[u];
            in[v]--;
            if(in[v]==0)
            Q.push(v);
        }
    }
}
void dfs(int u)
{
    vis[u]=1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(!vis[v]) dfs(v);
        if(out[u]) f[u][0]+=(f[v][0]+1)*(double)w[i];
    }
    double sum=f[u][0];
    if(out[u]) f[u][0]=(double)f[u][0]/out[u];
    if(!out[u]) return;
    for(int i=head[u];i;i=nxt[i])
    {   
        int v=to[i];
        if(out[u]-w[i]>0) f[u][1]=max(f[u][1],(double)(sum-(f[v][0]+1)*(double)w[i])/(out[u]-w[i]));
    }
    mx=max(mx,(f[u][1]-f[u][0])*p[u]);
}
int main()
{
    freopen("secretbase.in","r",stdin);
    freopen("secretbase.out","w",stdout);
    scanf("%d%d",&n,&m);
    mx=0.0;
    for(int i=1;i<=m;i++)
    {
        int u,v,ww;
        scanf("%d%d%d",&u,&v,&ww);
        u++,v++; in[v]++;
        out[u]+=ww;
        U[i]=u; V[i]=v; W[i]=ww; 
        build(u,v,ww);
    }   
    double  ans=0;
    for(int i=1;i<=n;i++)
    {
        vis[i]=0; p[i]=0.0;
    }
    p[1]=1.0;
    getp();
    for(int i=1;i<=n;i++)
    {
        vis[i]=0; f[i][0]=f[i][1]=0.0;
    }
    dfs(1);
    printf("%0.6lf\n",f[1][0]+mx);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值