洛谷题单指南-基础线性代数-P2973 [USACO10HOL] Driving Out the Piggies G

原题链接:https://www.luogu.com.cn/problem/P2973

题意解读:

给定一张无向图:N 个点M 条边

炸弹从 1 号城市开始。每一小时:以概率P=p/q在当前城市爆炸,以概率1−P随机选择一条边走到相邻城市(每条边等概率)。

问题:最终炸弹在每个城市爆炸的概率是多少

解题思路:(题解由Chatgpt生成)

路径长度无限:

可能出现

1
1→2
1→2→1
1→2→1→2
...

因此:无法简单 DP,需要求无限过程的稳态概率

关键技巧:把问题转化为期望访问次数

image

image

image

image

n个未知数,n个方程,高斯消元即可。

image

100分代码:(代码由Chatgpt生成)

#include <bits/stdc++.h>
using namespace std;

const int N = 305;
const double EPS = 1e-8;

// 增广矩阵 a,规模 n * (n+1),下标从 1 开始使用
// a[i][1..n] 为系数,a[i][n+1] 为常数项
double a[N][N];

// 无向图邻接表与度数
vector<int> g[N];
int deg[N];

int n, m;
double P; // P = p / q

// 高斯消元(带部分主元),返回:
//  1 -> 唯一解;0 -> 无穷多解;-1 -> 无解
int gauss()
{
    int row = 1;

    // 枚举列,逐步把主元行放到 row 位置
    for(int col = 1; col <= n; col++)
    {
        int t = row;

        // 在当前列从 row..n 选择绝对值最大的作为主元
        for(int i = row + 1; i <= n; i++)
            if(fabs(a[i][col]) > fabs(a[t][col]))
                t = i;

        // 该列全为 0,无法形成主元,跳过
        if(fabs(a[t][col]) < EPS) continue;

        // 把主元行交换到 row
        for(int i = col; i <= n + 1; i++)
            swap(a[t][i], a[row][i]);

        // 主元行归一化,使 a[row][col] = 1
        for(int i = n + 1; i >= col; i--)
            a[row][i] /= a[row][col];

        // 用当前主元消去其他行的该列
        for(int i = 1; i <= n; i++)
        {
            if(i == row) continue;

            for(int j = n + 1; j >= col; j--)
                a[i][j] -= a[row][j] * a[i][col];
        }

        row++;
    }

    // row <= n 说明存在自由元,需判断是否矛盾
    if(row <= n)
    {
        for(int i = row; i <= n; i++)
            if(fabs(a[i][n + 1]) > EPS)
                return -1;

        return 0;
    }

    return 1;
}

int main()
{
    int p, q;
    cin >> n >> m >> p >> q;

    P = (double)p / q;

    // 读入无向边,建图
    for(int i = 1; i <= m; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    // 统计每个点度数
    for(int i = 1; i <= n; i++)
        deg[i] = g[i].size();

    // 构造线性方程:
    // 设 x[i] 为到达目标(或被选中)的概率/期望值(与题意对应),
    // 转移:从 i 出发,以 P 结束,否则以 1-P 随机走到邻居。
    // 具体形式为:
    //   x[i] - sum_{j in g[i]} (1-P)/deg[j] * x[j] = (i==1 ? 1 : 0)
    // 右端常数项根据题意设置(起点 1 的常数为 1,其余为 0)。
    // 注:系数中出现 deg[j],是根据题目给定的转移方式整理而来。
    for(int i = 1; i <= n; i++)
    {
        // x[i] 系数为 1
        a[i][i] = 1;

        // 邻居节点对当前方程的贡献
        for(int j : g[i])
            a[i][j] -= (1 - P) / deg[j];

        // 常数项
        if(i == 1) a[i][n + 1] = 1;
        else a[i][n + 1] = 0;
    }

    // 求解线性方程组
    gauss();

    // 输出答案:题意要求乘以 P(结束事件的概率)
    for(int i = 1; i <= n; i++)
        printf("%.9f\n", a[i][n + 1] * P);

    return 0;
}

 

posted @ 2026-03-17 14:58  hackerchef  阅读(1)  评论(0)    收藏  举报