对状态转移方程的理解(【HNOI2013】游走&【hdu4035】Maze)

先来看一道例题:

【HNOI2013】游走

一个无向连通图,顶点从1编号到N,边从1编号到M。
小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。
现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。

【分析】

d [ i ] d[i] d[i]为走上 i i i号边的期望次数,则对 d [ i ] d[i] d[i]从小到大排序后,有:
a n s = ∑ i = 1 m d [ i ] ∗ ( m − i + 1 ) ans=\sum_{i=1}^md[i]*(m-i+1) ans=i=1md[i](mi+1)
关键是求 d [ i ] d[i] d[i]。我们发现,通过一条边的次数与到达两顶点的有关。于是,设 f [ i ] f[i] f[i]为通过 i i i号点的期望次数,则( d [ i ] d[i] d[i] i i i的度数):
f [ i ] = ∑ j ∈ s o n [ i ] f [ j ] d [ j ] + 1 f[i]=\sum_{j \in son[i] } \frac{f[j]}{d[j]}+1 f[i]=json[i]d[j]f[j]+1
然而,由于图有环,转移顺序并不明确。所以说,我们把它当做一般的方程看待,其中 f [ i ] f[i] f[i] f [ j ] f[j] f[j]为未知数。
对应每一个 i ∈ [ 1 , n − 1 ] i \in [1,n-1] i[1,n1],都有上面的方程。这样,我们就得到了 n − 1 n-1 n1方程组
方程组?那就拿高斯消元乱搞一下不就好了?!

【代码】
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int mn = 505;
const double eps = 1e-6;
vector<int > g[mn];
double f[mn][mn], d[mn], b[mn];
struct edge{
    int a, b;
    double val;
    bool operator <(const edge x) const {return val < x.val;}
}e[mn * mn];
inline int dcmp(double x)
{
    if(fabs(x) < eps)   return 0;
    return x < 0 ? -1 : 1;
}
inline void gauss(int n)
{
    for(int i=1;i<=n;++i) {
        int p=i;
        for(int k=i+1;k<=n;++k) if(fabs(f[k][i])>fabs(f[p][i])) p=k;
        if(i!=p)
        {
            swap(b[i], b[p]);
            for(int j = 1; j <= n; j++)
                swap(f[i][j], f[p][j]);
        }
        for(int k=i+1;k<=n;++k) {
            double h=f[k][i]/f[i][i];
            b[k]-=h*b[i];
            for(int j=i;j<=n;++j) f[k][j]-=h*f[i][j];
        }
    }
    for(int i=n;i>=1;--i) {
        for(int j=i+1;j<=n;++j) b[i]-=d[j]*f[i][j];
        d[i]=b[i]/f[i][i];
    }
}
int main()
{
    int n, m, i, j, x, y;
    scanf("%d%d", &n, &m);
    for(i = 1; i <= m; i++)
        scanf("%d%d", &x, &y), g[x].push_back(y), g[y].push_back(x), e[i] = (edge) {x, y, 0};
    for(i = 1; i < n; i++)
    {
        f[i][i] = 1.0;
        int siz = g[i].size();
        for(j = 0; j < siz; j++)
        {
            int to = g[i][j];
            if(to != n) f[i][to] = -1.0 / (double)(g[to].size());
        }
    }
    b[1] = 1, gauss(n - 1);
    for(i = 1; i <= m; i++)
        e[i].val = d[e[i].a] / (double)(g[e[i].a].size()) + d[e[i].b] / (double)(g[e[i].b].size());
    sort(e + 1, e + 1 + m);
    double ans = 0;
    for(i = 1; i <= m; i++)
        ans += (m - i + 1) * e[i].val;
    printf("%.3lf\n", ans);
}

【hdu4035】Maze

给定一棵树,每一个节点有两个值 k i k_i ki e i e_i ei,表示在i号节点,有 k i % k_i\% ki%的概率被传送至节点1,有 e i % e_i\% ei%的概率走出这棵树。现在有一个人从1号节点出发,每到一个节点 i i i,他会随机选择一条相邻边走,求他走出这棵树期望需要多少步。

【分析】

不难想到设 f [ i ] f[i] f[i]表示从 i i i出发走出迷宫的期望步数,则( d [ i ] d[i] d[i]表示 i i i节点的度数):
f [ i ] = ∑ j ∈ s o n [ i ] ( f [ j ] + 1 ) ∗ ( 1 − k [ i ] − e [ i ] ) d [ i ] + ( f [ f a [ i ] ] + 1 ) ∗ ( 1 − k [ i ] − e [ i ] ) d [ i ] + e [ i ] ∗ 0 + k [ i ] ∗ f [ 1 ] f[i]=\sum_{j \in son[i]}(f[j]+1)*\frac{(1-k[i]-e[i])}{d[i]} + (f[fa[i]]+1)*\frac{(1-k[i]-e[i])}{d[i]}+e[i]*0+k[i]*f[1] f[i]=json[i](f[j]+1)d[i](1k[i]e[i])+(f[fa[i]]+1)d[i](1k[i]e[i])+e[i]0+k[i]f[1]
由于他可以随便乱走,所以我们依旧无法确定状态的转移顺序。所以如果不再变形,则只能用高斯消元。
然而,这道题的规模不允许我们这样搞( n ≤ 10000 n \leq 10000 n10000)。所以我们得在这个方程上搞些事情。
若按照树形dp的方式做,那么 f [ f a [ i ] ] f[fa[i]] f[fa[i]]为未知状态,而 f [ j ] , j ∈ s o n [ i ] f[j], j \in son[i] f[j],json[i]为已知状态。所以,我们设:
f [ i ] = a [ i ] ∗ f [ f a [ i ] ] + b [ i ] ∗ f [ 1 ] + c [ i ] f[i]=a[i]*f[fa[i]]+b[i]*f[1]+c[i] f[i]=a[i]f[fa[i]]+b[i]f[1]+c[i]
将i换为 j , j ∈ s o n [ i ] j,j \in son[i] j,json[i],令 p [ i ] = ( 1 − k [ i ] − e [ i ] ) d [ i ] p[i]=\frac{(1-k[i]-e[i])}{d[i]} p[i]=d[i](1k[i]e[i]),代入原方程:
f [ i ] = ∑ j ∈ s o n [ i ] ( a [ j ] ∗ f [ i ] + b [ j ] ∗ f [ 1 ] + c [ j ] + 1 ) ∗ p [ i ] + ( f [ f a [ i ] ] + 1 ) ∗ p [ i ] + f [ 1 ] ∗ k [ i ] f[i]=\sum_{j \in son[i]}(a[j]*f[i]+b[j]*f[1]+c[j]+1)*p[i]+(f[fa[i]]+1)*p[i]+f[1]*k[i] f[i]=json[i](a[j]f[i]+b[j]f[1]+c[j]+1)p[i]+(f[fa[i]]+1)p[i]+f[1]k[i]
t [ i ] = 1 − ∑ j ∈ s o n [ i ] a [ j ] ∗ p [ i ] t[i]=1-\sum_{j \in son[i]}a[j]*p[i] t[i]=1json[i]a[j]p[i],整理得:
f [ i ] = p [ i ] t [ i ] f [ f a [ i ] ] + ∑ j ∈ s o n [ i ] b [ j ] ∗ p [ i ] + k [ i ] t [ i ] ∗ f [ 1 ] + ∑ j ∈ s o n [ i ] c [ j ] ∗ p [ i ] + p [ i ] t [ i ] f[i]=\frac{p[i]}{t[i]}f[fa[i]]+\frac{\sum_{j \in son[i]}b[j]*p[i] + k[i]}{t[i]}*f[1] + \frac{\sum_{j \in son[i]} c[j]*p[i]+p[i]}{t[i]} f[i]=t[i]p[i]f[fa[i]]+t[i]json[i]b[j]p[i]+k[i]f[1]+t[i]json[i]c[j]p[i]+p[i]
所以:
{ a [ i ] = p [ i ] t [ i ] b [ i ] = ∑ j ∈ s o n [ i ] b [ j ] ∗ p [ i ] + k [ i ] t [ i ] c [ i ] = ∑ j ∈ s o n [ i ] c [ j ] ∗ p [ i ] + p [ i ] t [ i ] \begin{cases} a[i]=\frac{p[i]}{t[i]}\\ b[i]=\frac{\sum_{j \in son[i]}b[j]*p[i] + k[i]}{t[i]}\\ c[i]=\frac{\sum_{j \in son[i]} c[j]*p[i]+p[i]}{t[i]} \end{cases} a[i]=t[i]p[i]b[i]=t[i]json[i]b[j]p[i]+k[i]c[i]=t[i]json[i]c[j]p[i]+p[i]
一次树形dp即可把a、b、c求出来。答案为:
f [ 1 ] = b [ 1 ] ∗ f [ 1 ] + c [ 1 ] ⟺ f [ 1 ] = c [ 1 ] 1 − b [ 1 ] f[1]=b[1]*f[1]+c[1] \Longleftrightarrow f[1]=\frac{c[1]}{1-b[1]} f[1]=b[1]f[1]+c[1]f[1]=1b[1]c[1]
如果计算过程中出现了除0的情况,输出“impossible”。(若向某一个方向走的概率达到了1,则不可能走出树)
【代码】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int mn = 10005;
const double eps = 1e-10;
vector<int >g[mn];
double a[mn], b[mn], c[mn], e[mn], k[mn], p[mn];
int d[mn];
bool flag;
inline void dp(int s, int fa)
{
    double tmp = 0;
    a[s] = p[s], b[s] = k[s], c[s] = 1 - k[s] - e[s];
    int m = g[s].size();
    for(int i = 0; i < m; i++)
    {
        int to = g[s][i];
        if(to != fa)
        {
            dp(to, s);
            if(flag)
                return;
            tmp += a[to] * p[s], b[s] += b[to] * p[s], c[s] += c[to] * p[s];
        }
    }
    tmp = 1.0 - tmp;
    if(fabs(tmp) < eps)
    {
        flag = 1;
        return;
    }
    a[s] /= tmp, b[s] /= tmp, c[s] /= tmp;
}
inline void init(int n)
{
    for(int i = 1; i <= n; i++)
        g[i].clear();
    flag = 0;
}
int main()
{
    int T, n, i, x, y;
    scanf("%d", &T);
    for(int tt = 1; tt <= T; tt++)
    {
        scanf("%d", &n), init(n);
        for(i = 1; i < n; i++)
            scanf("%d%d", &x, &y), g[x].push_back(y), g[y].push_back(x);
        for(i = 1; i <= n; i++)
            scanf("%lf%lf", &k[i], &e[i]), k[i] /= 100.0, e[i] /= 100.0, p[i] = 1 - k[i] - e[i];
        for(i = 1; i <= n; i++)
            d[i] = g[i].size();
        for(i = 1; i <= n; i++)
            p[i] /= (double)(d[i]);
        dp(1, 0), printf("Case %d: ", tt);
        if(flag || fabs(1 - b[1]) < eps)
            puts("impossible");
        else
            printf("%.6lf\n", c[1] / (1 - b[1]));
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值