[日常训练] 武馆的挑战

【问题描述】

  • 在复杂的武林中,有这样一个恶人:他身手不凡,经常去各武馆闹事,并以此为乐。他的存在,让每个武馆都不得安宁。
  • 这一天,某武馆不幸收到了他的挑战书。为了维护武馆的名声和尊严,武馆主和武馆里的所有武士发誓要倾尽全力击败他。
  • 武馆是由 N N 个直接或间接相通的房间构成的,房间标号为 0,1,,N1,某些房间之间有走廊可双向互通。通过研究恶人之前闹事的过程,武官主总结了他的一些特殊癖好:
    • 在去武馆闹事之前,总是先深入研究过武馆的地图。
    • 在武馆内走动的过程中会遵循下列规则:
      • 总是从正厅( 0 0 号房间)出发。
      • 不会重复经过同一个房间。
      • 他不想白白浪费自己的体力,因此任一时刻,若他之前经过的所有房间依次是 0,P1,P2,,Pr,则这个序列一定是从 0 0 号房间到 Pr 号房间的最短路。
      • 若到达某一点的两条路径长度相等,则他会从后向前依次比较这两条路径上的房间编号,直到找到不相同的编号,则编号小的房间所属的路径在他看来更短(从而另一条路径就一定不是最短路)。
      • 在满足上述条件的情况下,下一步若有多个房间可以选择,他将等概率选取某一房间。
      • 他将一直不停地移动,直到无法在上述条件下移动为止,这时,他就认为自己击败了整个武馆,然后大摇大摆地离开。
  • 为了不让恶人顺利离开武馆,武馆主打算将 P P 个武士安排在某些房间,当恶人经过安排了武士的房间时,武士们和恶人会发生战斗。每个房间可以没有武士,也可以有一个甚至多名武士(以多打少?对付恶人就应该不择手段)。武馆主已经根据敌我的实力和各房间不同的环境制作了一张概率表,详细列举了在某个房间安排一定人数的武士时,在该房间里击败恶人的概率。
  • 现在,他们想知道,击败恶人、保住武馆声誉的最大概率是多少。

【输入格式】

  • 第一行是两个整数 n,m,表示武馆内的房间数和走廊数。接下来 m m 行,第 i 行是三个整数 ai,bi,li a i , b i , l i ,表示第 i i 个走廊连接房间 ai 和房间 bi b i ,长度为 li l i 。之后一行是一个整数 p p ,表示武士数。接下来 n 行,每行 p p 0 1 1 之间的实数(可以等于 0 1 1 ),描述了概率表 pt,其中第 i i 行第 j 个数为 pt[i][j] p t [ i ] [ j ] ,表示在第 i i 个房间安排 j 名武士击败恶人的概率(安排 0 0 个武士时概率必然为 0,因此省略;安排人数越多,击败恶人的概率必然不会降低)。

【输出格式】

  • 只有一个实数,表示击败恶人的概率(百分比,保留两位小数)。

【输入样例】

  • 4 4
    0 1 1
    0 2 2
    1 3 3
    2 3 1
    2
    0.01 0.1
    0.5 0.8
    0.5 0.8
    0.7 0.9
    0 0

【输出样例】

  • 60.00

【样例解释】

  • 1,3 1 , 3 号房间各安排一名武士,则击败恶人的概率为:
    0.5×pt[1][1]+0.5×1×pt[3][1]=60.00% 0.5 × p t [ 1 ] [ 1 ] + 0.5 × 1 × p t [ 3 ] [ 1 ] = 60.00 %

【数据规模】

  • 30% 30 % 的数据, n10 n ≤ 10
  • 60% 60 % 的数据, n100 n ≤ 100
  • 100% 100 % 的数据, 1n1000,0m100000p501li10000 1 ≤ n ≤ 1000 , 0 ≤ m ≤ 10000 , 0 ≤ p ≤ 50 , 1 ≤ l i ≤ 10000

  • 中考后第一篇博文,一个新的开始。

【分析】

  • 由题意先跑个 SPFA S P F A ,求出起点到每个节点的最短路 dis[i] d i s [ i ]
  • 对于节点 y y 的前一步是满足 dis[x]+lenxy=dis[y] 中最小的 x x
  • 我们不妨把 x 看成 y y 的父亲,建出一棵树。
  • 那么恶人被打败即为从根节点到任一叶子结点的路径,存在有一点的武士把恶人打败。
  • 这样并不好做,考虑把问题转化:
  • f[x][i] 表示在节点 x x i 个武士,恶人获胜的最小概率。
  • 初值: f[x][i]=1 f [ x ] [ i ] = 1 (节点 x x 不放武士)。
  • 转移:(用 f[j] 记下转移前的 f[x][j] f [ x ] [ j ] s s 表示当前子树个数)
    1. f[x][i+j]=min{s1sf[i]+f[y][j]s}(在子节点 y y j 个武士)。
    2. f[x][i+j]=min{f[i]×(1pt[x][j])} f [ x ] [ i + j ] = min { f ′ [ i ] × ( 1 − p t [ x ] [ j ] ) } (在节点 x x j 个武士)。
    3. 答案: 1f[0][p] 1 − f [ 0 ] [ p ] (表示击败恶人的最大概率) 。
    4. 时间复杂度 O(NP2) O ( N P 2 )

【代码】

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <algorithm>

using namespace std;

inline int get()
{
    char ch; int res = 0; bool flag = false;
    while (!isdigit(ch = getchar()) && ch != '-');
    (ch == '-' ? flag = true : res = ch ^ 48);
    while (isdigit(ch = getchar())) 
        res = res * 10 + ch - 48;
    return flag ? -res : res;
} 

const int N = 1005, M = 2005, P = 55;
const int Maxn = 0x3f3f3f3f;

int n, m, p;
double pt[N][P], f[N][P], ff[P];
int dis[N], h[N * 100]; bool vis[N];
struct Edge
{
    int to, cst; Edge *nxt;
};
Edge t[M], *lst[N], *T = t;
Edge q[M], *rst[N], *Q = q;

inline void Link(int x, int y, int z)
{
    (++T)->nxt = lst[x]; lst[x] = T; T->to = y; T->cst = z;
    (++T)->nxt = lst[y]; lst[y] = T; T->to = x; T->cst = z;
}

inline void Rink(int x, int y)
{
    (++Q)->nxt = rst[x]; rst[x] = Q; Q->to = y;
}

template <class T> inline void CkMin(T &x, T y) {if (x > y) x = y;}

inline void Spfa()
{
    memset(dis, Maxn, sizeof(dis));
    int t = 0, w = 1, x, y; dis[h[1] = 0] = 0; 
    while (t < w)
    {
        vis[x = h[++t]] = false;
        for (Edge *e = lst[x]; e; e = e->nxt)
            if (dis[y = e->to] > dis[x] + e->cst)
            {
                dis[y] = dis[x] + e->cst;
                if (!vis[y]) vis[h[++w] = y] = true;
            }
    }
    for (int i = 1; i < n; ++i)
    {
        x = i; int res = Maxn;  
        for (Edge *e = lst[x]; e; e = e->nxt)
            if (dis[x] == dis[y = e->to] + e->cst) CkMin(res, y);
        Rink(res, x);
    }
}

inline void TreeDp(int x)
{
    for (Edge *e = rst[x]; e; e = e->nxt)
        TreeDp(e->to);
    int y, s = 0;
    for (int i = 0; i <= p; ++i) f[x][i] = 1;
    for (Edge *e = rst[x]; e; e = e->nxt)
    { 
        ++s; y = e->to;
        for (int i = 0; i <= p; ++i) ff[i] = 1;
        //注意这里 ff[i] 初值为 1
        //我们应保证恶人走到各个房间的情况都被算入概率 
        for (int i = 0; i <= p; ++i)
            for (int j = 0, jm = p - i; j <= jm; ++j)
                CkMin(ff[i + j], f[x][i] * (s - 1) / s + f[y][j] / s);
        for (int i = 0; i <= p; ++i) f[x][i] = ff[i];
    }
    for (int i = 0; i <= p; ++i) ff[i] = f[x][i];
    for (int i = 0; i <= p; ++i)
        for (int j = 0, jm = p - i; j <= jm; ++j)
            CkMin(f[x][i + j], ff[i] * (1 - pt[x][j]));
}

int main()
{
    freopen("challenge.in", "r", stdin);
    freopen("challenge.out", "w", stdout);

    n = get(); m = get(); int x, y;
    while (m--)
    {
        x = get(); y = get();
        Link(x, y, get());
    }
    Spfa();
    p = get();
    for (int i = 0; i < n; ++i)
        for (int j = 1; j <= p; ++j)
            scanf("%lf", &pt[i][j]);
    TreeDp(0);
    printf("%.2lf", (1 - f[0][p]) * 100);

    fclose(stdin); fclose(stdout);
    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值