洛谷 矩阵取数游戏 dp+高精度

本文介绍了一种在洛谷平台上的矩阵取数游戏中使用的优化算法,通过结合动态规划(DP)与高精度计算,实现了对游戏策略的有效求解。文章详细展示了算法的具体实现过程,包括初始化、状态转移方程、以及最终结果的输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷 矩阵取数游戏 dp+高精度

#include <iostream>
#include <cstring>
#include <algorithm>
#include <stdio.h>
#include <cmath>
#define Mod 10000
#define MAXN 85

using namespace std;

int n,m;
int a[MAXN];

struct HP
{
    int p[505],len;
    HP()
    {
        memset(p,0,sizeof p);
        len=0;
    }
    void print()
    {
        printf("%d",p[len]);
        for(int i=len-1;i>0;i--)
        {
            if(p[i]==0)
            {
                printf("0000");
                continue;
            }
            for(int k=10;k*p[i]<Mod;k=k*10)
            {
                printf("0");
            }
            printf("%d",p[i]);
        }
    }
}dp[MAXN][MAXN],base[MAXN],ans;

HP operator + (const HP &a,const HP &b)
{
    HP c;
    c.len=max(a.len,b.len);
    int x=0;
    for(int i=1;i<=c.len;i++)
    {
        c.p[i]=a.p[i]+b.p[i]+x;
        x=c.p[i]/Mod;
        c.p[i]%=Mod;
    }
    if(x>0)
        c.p[++c.len]=x;
    return c;
}

HP operator * (const HP &a,const int &b)
{
    HP c;
    c.len=a.len;
    int x=0;
    for(int i=1;i<=c.len;i++)
    {
        c.p[i]=a.p[i]*b+x;
        x=c.p[i]/Mod;
        c.p[i]%=Mod;
    }
    while(x>0)
    {
        c.p[++c.len]=x%Mod;
        x/=Mod;
    }
    return c;
}

HP max(const HP &a,const HP &b)
{
    if(a.len>b.len)
        return a;
    else if(a.len<b.len)
        return b;
    for(int i=a.len;i>0;i--)
    {
        if(a.p[i]>b.p[i])
            return a;
        else if(a.p[i]<b.p[i])
            return b;
    }
    return a;
}

void BaseTwo()
{
    base[0].p[1]=1;
    base[0].len=1;
    for(int i=1;i<=m+2;i++)
    {
        base[i]=base[i-1]*2;
    }
}

int main()
{
    cin>>n>>m;
    BaseTwo();
    while(n--)
    {
        memset(dp,0,sizeof dp);
        for(int i=1;i<=m;i++)
            cin>>a[i];
        for(int i=1;i<=m;i++)
        {
            for(int j=m;j>=i;j--)
            {
                dp[i][j]=max(dp[i][j],dp[i-1][j]+base[m-j+i-1]*a[i-1]);
                dp[i][j]=max(dp[i][j],dp[i][j+1]+base[m-j+i-1]*a[j+1]);
            }

        }
        HP h;
        for(int i=1;i<=m;i++)
        {
            h=max(h,dp[i][i]+base[m]*a[i]);
        }
        ans=ans+h;
    }
    ans.print();
    return 0;
}

<think>我们分析 P3232 [HNOI2013]游走这道题。题目要求在一个无向连通图上进随机游走,从节点1出发,到达节点n结束。每次随机选择一条与当前节点相连的边走到下一个节点,并获得该边的分(分为边的编号)。现在需要对边重新编号(1~m),使得总分的期望值最小。注意,我们需要最小化的是总分的期望值。 ### 问题分析 1. **目标**:重新给边编号(1~m,分即为编号),使得从节点1随机游走到节点n所经过的边的编号之和的期望值最小。 2. **关键观察**:期望值最小的策略显然是将经过概率大的边赋予小的编号(因为期望是边权乘以经过概率的和,所以边权小则期望小,但这里边权就是编号,所以我们要让经过概率大的边编号小)。 3. **问题转化**:因此,我们需要计算每条边的期望经过次。然后,将期望经过次大的边赋予较小的编号(即1,2,...,m中较小的),而期望经过次小的边赋予较大的编号。这样,总期望和就会最小。 ### 解题思路 1. **计算每条边的期望经过次**:设一条边$e=(u,v)$的期望经过次为$f(e)$。那么,总期望和为$\sum_{e} f(e) \times \text{编号}(e)$。根据排序不等式,当$f(e)$大的边赋予小的编号时,总和最小。因此,问题转化为求每条边的期望经过次2. **边的期望经过次与点的关系**:对于无向图中的边$(u,v)$,其期望经过次可以表示为: $$f(u,v) = \frac{g(u)}{d(u)} + \frac{g(v)}{d(v)} \quad \text{当} \quad u \neq n \quad \text{且} \quad v \neq n$$ 其中,$g(u)$表示经过节点$u$的期望次,$d(u)$是节点$u$的度。但是注意,当游走到节点n时停止,所以节点n不会再往外走,因此边$(u,n)$的期望经过次只包含从$u$到$n$的那一次(且$n$不会往外走,所以不会从$n$走到$u$)。实际上,更精确的公式是: - 从$u$到$v$的期望次等于在节点$u$时选择边$(u,v)$的概率乘以到达$u$的期望次,再加上在节点$v$时选择边$(v,u)$的概率乘以到达$v$的期望次(但注意,如果$v$是n,则不会从$v$走到$u$)。 - 因此,一般地,有: $$f(u,v) = \frac{g(u)}{d(u)} \cdot [v \neq n] + \frac{g(v)}{d(v)} \cdot [u \neq n]$$ 其中$[P]$是指示函,当条件$P$成立时为1,否则为0。 3. **计算节点的期望经过次**:我们需要计算每个节点$u$的期望经过次$g(u)$。注意起点是1,终点是n(到达n后停止)。 - 对于节点1:其期望经过次$g(1)$包括初始时刻的一次,以及可能从其他节点走回来。因此,我们需要建立方程组。 - 对于节点$u$($u\neq 1$且$u\neq n$): $$g(u) = \sum_{(v,u) \in E} \frac{g(v)}{d(v)}$$ 注意:这里求和是遍历所有指向$u$的边(无向图,所以就是所有与$u$相邻的节点$v$),并且$v$不能是$n$(因为到达$n$就停止了,不会从$n$再走到其他节点)。 - 对于节点1,因为一开始就在1,所以: $$g(1) = 1 + \sum_{(v,1) \in E} \frac{g(v)}{d(v)} \quad \text{(注意:v不能是n)}$$ 但是,这里有一个循环依赖:每个节点的值依赖于相邻节点的值。因此,我们需要建立线性方程组。 4. **方程组建立**:设节点$u$的期望经过次为$g(u)$,则: - $g(1) = 1 + \sum_{v \in N(1)} \frac{g(v)}{d(v)} \quad \text{(注意:这里v不能是n,但实际上如果1和n相邻,那么从v=n过来是不允许的,因为n不会往外走)}$ 但是,这个式子并不直接,因为1的相邻节点可能会依赖1。实际上,我们重新考虑: - 当在节点$u$时,下一步会以$1/d(u)$的概率走到它的每个邻居(除了节点n,因为走到n就停止了,所以节点n不会继续往外走,但是节点u可以是n吗?注意u=n时已经停止,所以不考虑从n出发)。因此,节点u的期望经过次等于所有邻居节点v(v≠n)的期望经过次乘以从v走到u的概率(即$1/d(v)$)的和,再加上初始状态(如果是起点1,则多一个1)。 - 因此,我们可以写出: 对于节点1:$g(1) = 1 + \sum_{(v,1) \in E, v \neq n} \frac{g(v)}{d(v)}$ 对于节点$u$($u\neq 1$且$u\neq n$):$g(u) = \sum_{(v,u) \in E, v \neq n} \frac{g(v)}{d(v)}$ 对于节点n:$g(n)=0$(因为到达n就停止了,不会经过n继续走,而且题目要求到达n结束,所以计算经过次时,我们只计算到达n的次?但是注意,我们经过n的次1次(最后一次),但这里我们定义$g(n)$为到达n的次,但是实际上在游走过程中,到达n就停止了,所以n的期望经过次就是1?这里需要澄清。) 然而,实际上,我们定义$g(u)$为从起点1开始到游走结束(到达n)的过程中,经过节点$u$的次包括多次经过)。但是,节点n只能经过一次(因为到达n就停止了),所以$g(n)=1$?不对,因为节点n可能是从不同的路径到达的,所以$g(n)$应该是到达n的概率和?不对,期望次。 实际上,到达n后停止,所以节点n只能被经过一次(因为一旦到达就停止,所以不会再次到达)。但是,期望次1吗?不一定,因为可能有多条路径到达n,所以期望次应该是1(因为最终一定会到达n,所以期望次1)。但是,在方程组中,我们如何体现? 重新考虑:节点n是终止节点,所以当我们到达n时,游走结束。因此,节点n的期望经过次就是1(因为最终一定会到达一次)。但是,在计算其他节点时,从其他节点走到n,那么对于边来说,从其他节点到n的边只会计入一次(因为到达n就停止了)。 因此,我们修正: - $g(n)=1$(因为最终一定会到达n,所以期望经过次1) - 节点1:$g(1) = 1 + \sum_{(v,1)\in E} \frac{g(v)}{d(v)} \cdot [v\neq n]$ 但是,节点1的初始状态算一次,然后从邻居节点(非n)走回来也会增加次。 - 对于其他节点$u$($u\neq 1$且$u\neq n$): $$g(u) = \sum_{(v,u)\in E} \frac{g(v)}{d(v)} \cdot [v\neq n]$$ 但是,这个方程组存在循环依赖,而且节点1的方程中包含了自身(因为无向图中1的邻居可能包含1吗?无向简单图,没有自环,所以不会包含1)。然而,节点1的方程中出现了$g(v)$,而$v$可能是1的邻居,这些邻居又可能包含1(形成环),所以我们需要解线性方程组。 然而,这里有一个问题:节点n的期望次1,那么节点n的方程应该怎么写?实际上,节点n不会继续往外走,所以它不会对邻居的期望次产生贡献(即不会通过$g(n)/d(n)$去贡献其他节点)。但是,节点n的期望次$g(n)$是1,这个1是确定的。 所以,我们重新定义方程组: 令$g(1)$表示经过节点1的期望次包括初始的那一次),$g(n)=1$(表示最终一定会经过n一次)。 对于节点$u$($u\neq 1$且$u\neq n$),有: $$g(u) = \sum_{v \in N(u)} \frac{g(v)}{d(v)} \quad \text{(注意:这里要求$v\neq n$,因为从$v$到$u$的转移必须要求$v$不是终止节点)}$$ 但是,节点1的方程:$g(1) = 1 + \sum_{v\in N(1), v\neq n} \frac{g(v)}{d(v)}$(因为初始在1,然后从邻居(非n)回来会增加次) 节点n的方程:$g(n)=1$(不参与其他节点的转移,因为到达n就停止了) 然而,这个方程组并不完整,因为节点1的邻居可能包含n吗?可以包含。如果节点1和n相邻,那么从节点1可以直接走到n。但是,在节点1的方程中,我们并没有考虑从1走到n的情况?实际上,节点1的期望次只计算停留在1的次,而走到n后游走结束,不会增加其他节点的次。所以,在节点1的方程中,我们只考虑从其他节点走到1的情况(不包括从n走到1,因为n不会往外走)。所以,如果1和n相邻,那么节点1走到n并不会影响节点1的期望次(因为节点1的期望次是停留在1的次,而走到n是离开1)。 实际上,更标准的做法是:将节点n视为吸收态(一旦到达就停留,不再转移)。那么,我们可以建立如下方程组: 对于每个节点$u$($u\neq n$): $$g(u) = [u=1] + \sum_{v \in N(u)} \frac{g(v)}{d(v)} \quad (v \neq n)$$ 注意:这里$[u=1]$表示如果$u$是1,则初始有1次(即一开始就在1),否则为0。但是,这个方程中,每个节点$u$的期望次等于所有邻居$v$(非n)的期望次除以$d(v)$(即从$v$走到$u$的概率)的和,再加上初始在1的那一次(仅当$u=1$时)。然而,这个方程并没有考虑节点$u$的邻居是n的情况?实际上,当$u$的邻居是n时,从$u$到n的转移会终止,所以不会增加其他节点的次。但是,在计算$g(u)$时,我们只考虑其他节点走到$u$的情况,而$u$自己走到邻居并不会影响$g(u)$(因为$g(u)$是经过$u$的次,每次经过$u$后,$u$会以均等的概率走到邻居,包括n,但是走到n后不会增加其他节点的次,所以我们在方程组中不考虑从$u$到n的转移,只考虑其他节点走到$u$)。 然而,这个方程组的建立需要再仔细考虑。实际上,更常见的做法是: 设$g(u)$表示从起点1开始,到达终点n的游走过程中,经过节点$u$的期望次(注意:到达n后停止,所以节点n只计算一次,且不会从n再转移出去)。 那么,对于节点1,有: $$g(1) = 1 + \sum_{v \in N(1), v \neq n} \frac{g(v)}{d(v)}$$ 为什么?因为一开始在1(所以+1),然后,当从邻居$v$(非n)走到1时,会增加1的经过次(每次从$v$走到1,都会使1的经过次增加1次,而$v$的期望次是$g(v)$,每次$v$有$1/d(v)$的概率走到1,所以是$g(v)/d(v)$)。 对于节点$u$($u\neq 1$且$u\neq n$): $$g(u) = \sum_{v \in N(u), v \neq n} \frac{g(v)}{d(v)}$$ 因为节点$u$的经过次全部来源于邻居节点(非n)的转移。 对于节点n: $$g(n)=1$$ 因为最终一定会到达n一次(且仅一次,因为到达就停止了,所以不会多次到达)。 但是,这个方程组有一个问题:节点1的方程中,$g(1)$依赖于$g(v)$(v是1的邻居),而v的方程又可能依赖于$g(1)$(因为无向图,有环)。所以我们需要解一个线性方程组。 另外,节点n的方程是已知的($g(n)=1$),所以我们可以将其代入其他方程。但是,在节点u的方程中,我们只考虑非n的邻居,所以实际上节点n不会出现在方程的右边(除了节点n自己,但节点n的方程已经独立)。 然而,节点n的期望次1,但其他节点在计算时,如果邻居有n,那么从该节点走到n会终止,所以不会增加其他节点的次?但是,在计算节点u(u≠n)的期望次时,我们只考虑其他节点走到u,而不考虑u走到其他节点。所以,节点u的期望次等于所有非n邻居节点v的期望次乘以$1/d(v)$(即从v走到u的概率)的和。 因此,方程组为: $$g(1) - \sum_{v \in N(1), v\neq n} \frac{g(v)}{d(v)} = 1$$ $$g(u) - \sum_{v \in N(u), v\neq n} \frac{g(v)}{d(v)} = 0 \quad (u \neq 1, u\neq n)$$ $$g(n)=1$$ 但是,节点n的邻居呢?在计算邻居节点(比如节点u,且u与n相邻)的期望次时,我们并没有考虑从n走到u的情况(因为n不会往外走),所以节点n的期望次1不会对任何其他节点产生贡献。因此,节点n的1只表示它自己被经过1次。 然而,这里有一个问题:节点1的方程中,如果1和n相邻,那么从1可以一步走到n,但是节点1的期望次中,并不包括1走到n这一步对节点1的贡献?实际上,节点1的期望次包括:初始在11次),以及从其他节点(非n)走到1的次。而如果1直接走到n,那么节点1的期望次就是1(因为初始在1,然后直接走到n,没有其他节点走到1)。所以,这个方程是合理的。 但是,节点n的期望次1是怎么来的?实际上,最终一定会到达n,所以$g(n)=1$。但是,在游走过程中,节点1可能直接走到n,也可能经过其他路径到达n。所以,节点n的期望次1是固定的。 因此,我们得到$n-1$个未知($g(1),g(2),\dots,g(n-1)$)的线性方程组($g(n)$已知为1,不需要解)。注意,节点n的邻居节点在计算时,如果$u$是n的邻居,那么$u$的方程中,其邻居$v$不包括n(因为$v\neq n$),所以不会出现$g(n)$。 5. **边的期望经过次**:得到每个节点的期望经过次$g(u)$后,对于一条边$e=(u,v)$(假设$u$和$v$都不是n),那么这条边的期望经过次为: $$f(e) = \frac{g(u)}{d(u)} + \frac{g(v)}{d(v)}$$ 因为从$u$经过这条边到$v$(概率为$1/d(u)$,且$u$的期望经过次为$g(u)$),以及从$v$经过这条边到$u$(概率为$1/d(v)$,且$v$的期望经过次为$g(v)$)。但是,如果其中一个是n呢? - 如果$u=n$,那么边$(n,v)$的期望经过次:因为从$v$走到$n$($n$不会往回走),所以只有从$v$到$n$的贡献:$f(e) = \frac{g(v)}{d(v)}$。 - 同理,如果$v=n$,则$f(e) = \frac{g(u)}{d(u)}$。 因此,统一写: $$f(e) = \frac{g(u)}{d(u)} \cdot [u \neq n] + \frac{g(v)}{d(v)} \cdot [v \neq n]$$ 6. **最小化总期望**:设我们给边重新编号为$w_1, w_2, \dots, w_m$($w_i$是互不相同的1~m的整数),则总期望为$\sum_{e} f(e) \cdot w(e)$。根据排序不等式,为了最小化这个和,我们应该将期望经过次$f(e)$大的边赋予小的$w(e)$(即小的编号)。因此,将边按$f(e)$从大到小排序,然后依次赋予编号$1,2,\dots,m$(即最大的$f(e)$赋予1,次大的赋予2,...,最小的赋予m)。 总期望的最小值为:$\sum_{i=1}^{m} f(e_i) \times i$,其中$f(e_i)$是排序后第$i$大的边的期望经过次(即赋予的编号为$i$)。 7. **解线性方程组**:方程组有$n-1$个方程(节点1和节点2~n-1),未知为$g(1),g(2),\dots,g(n-1)$。注意节点n的$g(n)=1$已知。方程组的形式为: $$g(u) - \sum_{v \in N(u), v\neq n} \frac{g(v)}{d(v)} = \begin{cases} 1, & \text{if } u=1 \\ 0, & \text{otherwise} \end{cases}$$ 注意,这里$u$的范围是$1$到$n-1$。这个方程组可以用高斯消元求解。 注意:每个节点$u$的方程中,系矩阵的构建: - 第$u$第$u$列:系1。 - 第$u$第$v$列($v$是$u$的邻居,且$v\neq n$):系为$-1/d(v)$。 - 常项:$u=1$时为1,否则为0。 但是,这里有一个细节:节点$u$的方程中,$v$是$u$的邻居,而$v$可能是1~n-1中的任意节点(除了n)。另外,节点$u$的邻居$v$的度$d(v)$是已知的。 由于$n\le 500$,高斯消元$O(n^3)$可以接受。 ### 推导公式总结 1. **定义**: - $g(u)$:节点$u$的期望经过次($u$从1到n-1),$g(n)=1$。 - $d(u)$:节点$u$的度包括与n相邻的边?是的,因为度就是相邻边的量,但是节点n不会往外走,所以计算度时,节点n的度我们不考虑?实际上,度在图中是固定的,每个节点的度输入时就确定了,包括节点n的度。但是,在方程中,节点n的邻居不会出现在方程右边,而节点n的度在计算边经过次时会用到(如果边与n相连))。 2. **线性方程组**($u=1,2,\dots,n-1$): $$g(u) - \sum_{v \in N(u), v\neq n} \frac{g(v)}{d(v)} = \begin{cases} 1, & u=1 \\ 0, & u \neq 1, u \neq n \end{cases}$$ 注意:节点$u$的邻居$v$必须满足$v\neq n$(即不考虑从n转移过来的情况)。 3. **边的期望经过次**(对于边$e=(u,v)$): $$f(e) = \frac{g(u)}{d(u)} \cdot [u \neq n] + \frac{g(v)}{d(v)} \cdot [v \neq n]$$ 4. **最小化总期望**:将边按$f(e)$降序排序,赋予编号$1,2,\dots,m$,总期望为: $$\text{ans} = \sum_{i=1}^{m} i \times f(e_{\text{rank } i})$$ ### 算法步骤 1. 读入无向图,记录每个节点的度$d$,以及邻接表(包括边)。 2. 建立线性方程组(未知$g(1),g(2),\dots,g(n-1)$): - 初始化一个$n-1$阶的系矩阵$A$和常向量$B$。 - 对于每个节点$u$($1\le u \le n-1$): $A[u][u] = 1$ (这里假设节点编号从1开始,但矩阵下标从0开始则要调整) 对于$u$的每个邻居$v$($v$在1~n-1之间,即$v\neq n$): $A[u][v] = -1/d(v)$ (注意:这里$v$是邻居节点,且$v\neq n$) $B[u] = 1$ 当且仅当$u=1$,否则为0。 - 注意:节点$u$的邻居$v$可能是节点n,此时跳过(因为$v=n$,不在未知范围内)。 3. 用高斯消元法求解方程组$A \times \vec{g} = \vec{B}$,得到$g(1),g(2),\dots,g(n-1)$。 4. 令$g(n)=1$。 5. 计算每条边$e=(u,v)$的期望经过次$f(e)$: $$f(e) = \frac{g(u)}{d(u)} \cdot [u \neq n] + \frac{g(v)}{d(v)} \cdot [v \neq n]$$ 6. 将边按$f(e)$从大到小排序。 7. 将排序后的边依次赋予编号$1,2,\dots,m$(即$f(e)$最大的边编号为1,次大的编号为2,...,最小的编号为m)。 8. 计算总期望:$\text{ans} = \sum_{i=1}^{m} i \times f(e_i)$,其中$e_i$是排序后第$i$条边。 ### 注意事项 - 节点$n$的期望次$g(n)$固定为1,不参与方程组求解。 - 在构建方程组时,节点$u$的邻居$v$如果等于$n$,则忽略(因为$n$不在未知中)。 - 高斯消元求解$n-1$元线性方程组,时间复杂度$O(n^3)$,$n\le 500$,所以$500^3=1.25e8$,在C++中可能需要注意优化(如使用部分主元高斯消元,避免精度问题)。 - 精度问题:使用`double`,注意高斯消元中的精度控制。 ### 代码框架(C++) ```cpp #include <bits/stdc++.h> using namespace std; const int N = 505; const double eps = 1e-8; int n, m; vector<int> G[N]; // 邻接表,存邻居节点(包括n) int deg[N]; // 度 double g[N]; // g[1..n-1]为未知,g[n]=1 double A[N][N]; // 增广矩阵,大小为(n-1)*(n)(最后一列是常) int u[N*N], v[N*N]; // 存储每条边 void gauss(int n) { // 高斯消元求解n阶线性方程组(实际未知为n-1个,这里n表示节点-1,即n-1个未知) int equ = n, var = n; // n个方程,n个未知(这里n就是n-1,因为节点1~n-1) for (int i = 1; i <= n; i++) { // 找到第i列中绝对值最大的 int r = i; for (int j = i+1; j <= n; j++) if (fabs(A[j][i]) > fabs(A[r][i])) r = j; if (fabs(A[r][i]) < eps) continue; // 无解或多解 if (r != i) swap(A[r], A[i]); // 归一化 for (int j = i+1; j <= var+1; j++) A[i][j] /= A[i][i]; A[i][i] = 1.0; // 消元 for (int j = 1; j <= equ; j++) { if (j == i) continue; double rate = A[j][i]; for (int k = i; k <= var+1; k++) { A[j][k] -= rate * A[i][k]; } } } // 回代(实际上增广矩阵最后一已经是解) for (int i = 1; i <= n; i++) { g[i] = A[i][var+1]; } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { scanf("%d%d", &u[i], &v[i]); G[u[i]].push_back(v[i]); G[v[i]].push_back(u[i]); deg[u[i]]++; deg[v[i]]++; } // 建立方程组:未知g[1]~g[n-1] // 初始化增广矩阵 memset(A, 0, sizeof(A)); // 第1个方程对应节点1,第n-1个方程对应节点n-1 for (int i = 1; i <= n-1; i++) { // 第i个方程对应节点i(注意:节点编号i,在矩阵中是第i) A[i][i] = 1.0; for (int j = 0; j < G[i].size(); j++) { int neighbor = G[i][j]; if (neighbor == n) continue; // 跳过邻居为n的情况 // 注意:邻居neighbor在1~n-1之间 A[i][neighbor] -= 1.0 / deg[neighbor]; } // 常项 if (i == 1) A[i][n] = 1.0; // 常项在增广矩阵的第n列(因为未知有n-1个,所以常项放在第n列) else A[i][n] = 0.0; } // 高斯消元求解,未知为n-1 gauss(n-1); // 设置g[n]=1 g[n] = 1.0; // 计算每条边的期望经过次 vector<double> f; for (int i = 1; i <= m; i++) { double cnt = 0; if (u[i] != n) cnt += g[u[i]] / deg[u[i]]; if (v[i] != n) cnt += g[v[i]] / deg[v[i]]; f.push_back(cnt); } // 将f从大到小排序 sort(f.begin(), f.end(), greater<double>()); double ans = 0; for (int i = 0; i < m; i++) { ans += f[i] * (i+1); // 第i大的边赋予编号i+1(因为编号从1开始) } printf("%.3f\n", ans); return 0; } ``` 注意:上面的高斯消元部分是一个简化的版本,实际实现时要注意精度和主元选。另外,上面的代码中增广矩阵的常项放在第n列(即下标为n的列),而未知的列下标从1到n-1。 ### 参考文献 [^1]: Luogu P3232 [HNOI2013]游走题目描述 [^2]: 题解中关于概率dp和高斯消元的应用 [^3]: 关于终止节点n的处理方式 [^4]: 输入输出格式说明 §§相关问题§§ 1. 如何在高斯消元中避免精度问题? 2. 排序不等式在期望问题中的应用还有哪些? 3. 无向图随机游走问题中,节点的期望访问次有哪些性质? 4. 如果图不是无向图,而是有向无环图,如何计算边的期望经过次? 5. 高斯消元法的时间复杂度如何优化?</think>### P3232 [HNOI2013]游走 题目分析 #### 问题描述 给定一个无向连通图($n$ 个顶点,$m$ 条边),从节点 1 开始随机游走:每次以均等概率选择当前节点的边移动到相邻节点,并获得该边的编号作为分。到达节点 $n$ 时游走结束。要求对边重新编号(1 到 $m$),使得总分的期望值最小。 **关键观察**: 期望值最小化策略是让经过概率高的边赋予小编号(根据排序不等式)。因此核心是计算**每条边的期望经过次**,再按期望次降序分配编号 1, 2, ..., m。 --- ### 解题思路 #### 步骤 1:建立节点期望方程 设 $g(u)$ 表示节点 $u$ 的期望经过次($u \in [1, n]$),需满足: 1. **起点特殊处理**:$g(1)$ 包含初始状态 $$g(1) = 1 + \sum_{\substack{v \in N(1) \\ v \neq n}} \frac{g(v)}{d(v)}$$ 2. **中途节点**($u \neq 1$ 且 $u \neq n$): $$g(u) = \sum_{\substack{v \in N(u) \\ v \neq n}} \frac{g(v)}{d(v)}$$ 3. **终点固定**:$g(n) = 1$(到达即终止) > **注**:$N(u)$ 是 $u$ 的邻居集合,$d(v)$ 是节点 $v$ 的度。终点 $n$ 不向任何节点转移[^3]。 #### 步骤 2:构建线性方程组 将 $g(1)$ 到 $g(n-1)$ 的方程整理为 $n-1$ 元线性方程组: $$\begin{cases} g(1) - \sum_{v \in N(1) \backslash \{n\}} \frac{g(v)}{d(v)} = 1 \\ g(u) - \sum_{v \in N(u) \backslash \{n\}} \frac{g(v)}{d(v)} = 0 & (2 \leq u \leq n-1) \end{cases}$$ **矩阵形式示例**(以 3 节点链 1-2-3 为例): $$\begin{bmatrix} 1 & -\frac{1}{d(2)} \\ -\frac{1}{d(1)} & 1 \end{bmatrix} \begin{bmatrix} g(1) \\ g(2) \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \end{bmatrix}$$ #### 步骤 3:计算边的期望经过次 对边 $e = (u, v)$: $$f(e) = \frac{g(u)}{d(u)} \cdot [u \neq n] + \frac{g(v)}{d(v)} \cdot [v \neq n]$$ 其中 $[P]$ 是指示函(条件 $P$ 成立时为 1,否则为 0)[^3]。 #### 步骤 4:最小化期望总和 1. 将边按 $f(e)$ **从大到小**排序 2. 分配编号:$f(e)$ 最大的边编号为 1,次大的为 2,...,最小的为 $m$ 3. 总期望最小值: $$E_{\min} = \sum_{i=1}^{m} i \cdot f(e_{\text{rank } i})$$ > **学原理**:排序不等式(逆序和最小)[^1]。 --- ### 算法流程 ```mermaid graph TD A[输入图结构] --> B[构建线性方程组] B --> C[高斯消元解 g1~gn-1] C --> D[计算边期望 f(e)] D --> E[按 f(e) 降序排序] E --> F[分配编号 1~m] F --> G[计算总期望 E_min] ``` #### 复杂度分析 - **高斯消元**:$O(n^3)$($n \leq 500$ 可接受) - **边处理**:$O(m \log m)$(排序) --- ### 关键公式推导 #### 1. 节点期望方程推导 设 $p(u)$ 为访问节点 $u$ 的概率,则期望次 $g(u) = \sum p(\text{路径经过 } u)$。由全期望公式: - 起点 $u=1$ 初始贡献 $+1$ - 其他节点 $u$ 的访问来自邻居转移: $$g(u) = [u=1] + \sum_{\substack{v \to u \\ v \neq n}} \overbrace{\frac{1}{d(v)}}^{\text{转移概率}} \cdot \overbrace{g(v)}^{\text{访问 } v \text{ 的期望}}$$ #### 2. 边期望公式推导 边 $(u,v)$ 被经过的两种情况: 1. $u \to v$:概率 $\frac{g(u)}{d(u)}$($u \neq n$) 2. $v \to u$:概率 $\frac{g(v)}{d(v)}$($v \neq n$) 故 $f(e) = \frac{g(u)}{d(u)} \cdot [u \neq n] + \frac{g(v)}{d(v)} \cdot [v \neq n]$ #### 3. 最小化策略证明 由排序不等式: $$\sum_{i=1}^m a_i b_i \geq \sum_{i=1}^m a_i b_{\sigma(i)} \quad (a,b \text{ 同序时最小})$$ 当 $a_i = f(e_i)$(降序)且 $b_i = i$(升序)时和最小[^1]。 --- ### 代码实现要点 ```cpp #include <bits/stdc++.h> using namespace std; const int N = 505; vector<int> G[N]; // 邻接表 int deg[N], u[N*N], v[N*N]; // 度 & 边信息 double A[N][N], g[N]; // 方程组 & 解 void gauss(int n) { // 解 n 阶方程组 for (int i = 1; i <= n; ++i) { int pivot = i; for (int j = i+1; j <= n; ++j) if (fabs(A[j][i]) > fabs(A[pivot][i])) pivot = j; swap(A[i], A[pivot]); for (int j = n+1; j >= i; --j) A[i][j] /= A[i][i]; for (int j = 1; j <= n; ++j) { if (j == i) continue; double factor = A[j][i]; for (int k = i; k <= n+1; ++k) A[j][k] -= factor * A[i][k]; } } for (int i = 1; i <= n; ++i) g[i] = A[i][n+1]; } int main() { // 读图 & 初始化度 int n, m; cin >> n >> m; for (int i = 1; i <= m; ++i) { cin >> u[i] >> v[i]; G[u[i]].push_back(v[i]); G[v[i]].push_back(u[i]); deg[u[i]]++, deg[v[i]]++; } // 构建方程组 (n-1 个未知) for (int i = 1; i < n; ++i) { A[i][i] = 1.0; for (int v : G[i]) { if (v == n) continue; // 跳过终点 A[i][v] = -1.0 / deg[v]; } A[i][n+1] = (i == 1) ? 1.0 : 0.0; // 常项 } gauss(n-1); // 解 g[1]~g[n-1] g[n] = 1.0; // 固定终点期望 // 计算边期望 vector<double> f; for (int i = 1; i <= m; ++i) { double cnt = 0; if (u[i] != n) cnt += g[u[i]] / deg[u[i]]; if (v[i] != n) cnt += g[v[i]] / deg[v[i]]; f.push_back(cnt); } // 排序 & 计算最小期望 sort(f.rbegin(), f.rend()); double ans = 0; for (int i = 0; i < m; ++i) ans += (i + 1) * f[i]; // 编号 1~m 分配 printf("%.3f\n", ans); return 0; } ``` > **精度提示**:高斯消元需使用部分主元法(`pivot`)避免除零和精度损失[^2]。 --- ### 相关思考 1. **为什么终点 $g(n)=1$?** 游走必以概率 1 到达 $n$,故期望次1[^3]。 2. **如何处理自环/重边?** 题目保证简单图,无需特殊处理[^4]。 3. **非连通图的影响?** 题目限定连通图,确保从 1 可到 $n$。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值