MAZE

49 篇文章 4 订阅

Problem A: MAZE

Time Limit: 1 Sec   Memory Limit: 128 MB
Submit: 3   Solved: 1
[ Submit][ Status][ Web Board]

Description

 一个含有n个点的迷宫是一棵树(一个任意两点之间都恰好有一条路径的无向图)。每个点都有一定的概率成为这个迷宫的入口和出口。
从这个迷宫走出去的方法是从入口开始进行深度优先搜索。如果当前有多个移动方案,那么等概率的选择移动方案中的一个。DFS的过程为以下的伪代码:
DFS(x)
if x == exit vertex then
finish search
flag[x] <- TRUE
random shuffle the vertices' order in V(x) // here all permutations have equal probability to be chosen
for i <- 1 to length[V] do
if flag[V[i]] = FALSE then
count++;
DFS(y);
count++;
V(x)是与x点相邻的点的序列。Flag数组初始时是全部为FALSE的。DFS 初始时从入口开始。当搜索结束时,变量count将会统计移动的次数。
你的任务是统计一个人从这个迷宫的入口走到出口步数的数学期望值。

Input

第一行一个数n,表示这个图的节点数。。
下面n-1行,每行包括两个数ai,bi,表示一条连接ai和bi的边。
保证给出的图是一棵树。
下面n行,每行包括两个非负整数xi,yi,表示选择i为入口的可能性和出口的可能性。

  选择i为入口的概率和选择i为出口的概率分别为xi/sumx和yi/sumy,sumx表示x的总和,sumy表示y的总和。sumx以及sumy均为正数且不超过10^6。

Output

输出期望的步数,要求误差不超过10^-9,输出保留两位小数

Sample Input

3
1 2
1 3
1 0
0 2
0 3

Sample Output

2.00


【分析】

树1 

就拿这颗简单的二叉树举例子,将1号节点作为起点,4号节点作为终点,它的长度期望值是多少呢? 
不妨计算一下,从1号点开始DFS,有1/2的几率进入3号点,而一旦进入3号点,它就必须遍历完整颗子树才会出来,而进入2号点之后还有1/2的几率进入5号点。而且不管如何都必须以进入正确的节点作为结束。 
因此总的期望为(1 + 1/2 * (1 * 2) + 1)+1/2 * (2*3) = 6

如果稍微机智一点,就能从上面那个期望的公式当中发现一些规律出来: 
从某一个节点到达另一个节点的路径期望值等于路径中所有可能被遍历的边数之和

打个表的话大概是这个样子:

终\起1234567
10446666
23046666
33406666
43140666
53146066
63416606
73416660

于是乎,我们可以从表格中总结出这样一条规律:

IF 终点为起点的祖先节点 Val = (为起点祖先的终点的子节点的儿子数+1)
ELSE   Val = (总结点数 - 1 - 终点的儿子数)
  • 1
  • 2
  • 3

举个例子, 
1是4的祖先节点,而2是4的祖先节点,且2是1的儿子,因此4->1的期望路径长=2->1的期望路径长=2的儿子数+1=4 
然后表格里各种666就是else的那种情况啦

于是乎 我们可以愉快的写下一个转移方程(伪代码):

for(int u = 0 ~ n) 
    for(int i = 0 ~ n except u)
        if (i 是 u 的祖先) sum += (find(u).son + 1) * u.st * i.ed;
        else sum += (n - 1 - i.son) * u.st * i.ed;`
  • 1
  • 2
  • 3
  • 4

复杂度在O(N^2)和O(N^3)之间,在100,000的数据量下不TLE才有鬼,所以需要改进 
首先,可以明确的是一个节点的祖先节点终究是有限的,更多的是情况2,也就是Val = (总结点数 - 1 - 终点的儿子数) 
所以我们可以先进行预处理sum += (n - 1 - son[u]) * (tst - st[u]) * ed[u]; 
然后再对所有u的祖先节点进行一次更新

x = u;
do{
    sum += (x.son + 1) * u.st * x.fa.ed;
    x = fa[x];
    sun -= (n - 1 - x.son) * u.st * x.ed;
}while(x != 根节点);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这样复杂度就大幅下降了,不过还是有可能会有一条链的情况,最坏复杂度依旧高达O(N^2),因此我们需要进一步优化 
从表中可以看出 每一个节点到达它父节点的期望路径值与它的子节点到达父节点的期望路径值是完全相等的,因此我们可以在DFS中储存节点u和u的所有子节点作为起点的概率和lf,然后一并进行处理。

这样复杂度就降到了O(N)

_____________________________________________________________________________________________

这道题目的数据我放水了,把精度调低了7位,真实数据并没有10^6而是只有10^3..本意是想让第一次出去比赛的同学感受一下自己明知道应该不会过,但是加了一些自己能做到的优化之后过了这道本身的确过不去的题目的感觉......然而...一个交的人都没有

考虑一件事情,比赛的时候,如果剩下的题目都不会做了,又有空的时间,难道就不做了吗?

就算你知道可能会T,为什么不考虑交一发会T的代码,如果T了,为什么不考虑考虑加上自己能想到的所有优化,万一过了那就是质的区别,碰到还剩下两三个钟头,剩下的题目感觉自己都不会做,剩下两三个钟头总不能用来看小姐姐和小哥哥吧...来都来了,题目看都看了,思考都思考过了,就算不行,交一发代码总比你坐着什么都不干的好

然后另外一件事情就是,我希望所有能够参赛的同学不要觉得自己只会dfs只会基础搜索觉得自己很差,基础搜索才是最根本的东西....少想想什么看起来很厉害很牛逼的操作...听别人说什么扫描线,网络流什么什么的东西...毕竟路要一步一步走...基础的东西才是以后做的最多的东西...也是可塑性最强的东西

【代码】

#include<iostream>
#include<string>
#include<cstdio>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<fstream>
using namespace std;

const int maxn = 100010;

int fa[maxn],son[maxn],st[maxn],ed[maxn],n;
long long sum = 0,tst = 0,ted = 0,lf[maxn];
vector<int> e[maxn];

void dfs1(int u,int f)
{
    son[u] = 0;
    fa[u] = f;
    for(int i = 0;i < e[u].size();++i)
	{
        int ff = e[u][i];
        if (ff == f) continue;
        dfs1(ff,u);
        son[u] += son[ff];
        son[u]++;
    }
}

void dfs2(int u)
{
    lf[u] = st[u];
    sum += (n - 1 - son[u]) * (tst - st[u]) * ed[u];
    for(int i = 0;i < e[u].size();++i)
	{
        int ff = e[u][i];
        if (ff == fa[u]) continue;
        dfs2(ff);
        lf[u] += lf[ff];
        sum += (son[ff] + 1 - (n - 1 - son[u])) * lf[ff] * ed[u];
    }
    if(u == 1) return;
}


int main()
{
	cin >> n;
    int l,r;
    for(int i = 1;i < n;++i)
	{
        cin >> l >> r;
        e[l].push_back(r);
        e[r].push_back(l);
    }
    for(int i = 1;i <= n;++i)
	{
        cin >> l >> r;
        st[i] = l;
        tst += l;
        ed[i] = r;
        ted += r;
    }
    dfs1(1,1);
    dfs2(1);
    printf("%.2f\n",sum * 1.0 / tst / ted);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值