29 HNOI2011 XOR和路径 题解

XOR和路径

题面

给定一个 \(N\) 个点,\(M\) 条边的无向连通图,其边的权值为非负整数。

该路径可以重复经过某些节点或边,当一条边在路径中出现多次时,其权值在计算 XOR 和时也应被重复计算相应多的次数。

具体来说,从 1 号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿着这条边走到下一个节点,重复这个过程直到走到 N 号节点为止,便得到一条从 1 号节点到 N 号节点的路径。

显然得到每条这样的路径的概率是不同的,并且每条这样的路径的 XOR 和也不一样。

现在请你求出该算法得到的路径的 XOR 和的期望值。

题解

考虑dp求解,设 \(f(x)\) 表示从 \(x\) 节点走到 \(N\) 节点的XOR和期望值,但是发现这个状态的转移方程好像写不出来,因为期望值可能是个小数,那你一个小数异或是没有意义的

考虑异或的性质:各个位互不影响

所以我们可以按位考虑最后的异或和,\(f(x)\) 表示从 \(x\) 节点走到 \(N\) 节点的XOR和第 \(k\) 位为 1 的概率

这里我们采用倒推的方式,就是实际上我们是 \(u \to v\) ,但方程中是从 \(v\) 转移到 \(u\)

转移方程:

\[\begin {align} &f_u = \frac 1 {deg_u} [ \sum_{e_i = 0} f_v + \sum_{e_i = 1} (1 - f_v)] \\ &deg_u \times f_u - \sum_{e_i = 0} f_v + \sum_{e_i = 1} f_v = \sum_{e_i = 1} 1 \end {align} \]

这里的 \(e_i\) 表示 \((u,v)\) 的边权 \(\& (1 << k)\) 的值

要注意的一点是,\(f_n = 0\) 这是已知的,所以我们只有 \(n - 1\) 个未知数以及方程,如果某个 \(v = n\) 的话,要特判一下

然后对于每一位都算一遍,最后按照每一位的概率乘上相对应的值即为最后答案

这题还有一点细节,图中可能有自环,此时这条边不能加两次,因为题目中有 “随机选择与当前节点相关联的某条边” 所以要特殊处理一下

时间复杂度为 \(O(30n^3)\)

code

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

using namespace std;

const int N = 110;

int n, m;
vector <pair <int, int> > e[N];
double a[N][N];
int deg[N];

//高斯消元
void Guss () {
    for (int i = 1; i < n; i ++) {
        int mx = i;
        for (int j = i + 1; j < n; j ++) {
            if (abs (a[j][i]) > abs (a[mx][i])) {
                mx = j;
            }
        }
        if (mx != i) swap (a[mx], a[i]);
        for (int j = 1; j < n; j ++) {
            if (i == j) continue;
            double mul = a[j][i] / a[i][i];
            for (int k = 1; k <= n; k ++) {
                a[j][k] -= a[i][k] * mul;
            }
        }
    }
}


int main () {
    cin >> n >> m;
    for (int i = 1; i <= m; i ++) {
        int x, y, z;
        cin >> x >> y >> z;
        //特殊处理自环的情况
        if (x == y) {
            e[x].push_back ({y, z});
            deg[x] ++;
        } else {
            e[x].push_back ({y, z});
            e[y].push_back ({x, z});
            deg[x] ++, deg[y] ++;
        }
        
    }
    double ans = 0;
    for (int i = 0; i <= 30; i ++) {
        memset (a, 0, sizeof a);
        for (int u = 1; u < n; u ++) {
            a[u][u] = deg[u];
            for (auto p : e[u]) {
                int v = p.first, val = (p.second >> i) & 1;
                if (v == n) {
                    if (val) a[u][n] ++;
                } else {
                    if (val) {
                        a[u][n] ++;
                        a[u][v] ++;
                    } else {
                        a[u][v] --;
                    }
                }
            }
        }
        Guss ();
        ans += (a[1][n] / a[1][1]) * (1 << i);
    }
    printf ("%.3lf\n", ans);
    return 0;
}
posted @ 2025-10-05 18:06  michaele  阅读(2)  评论(0)    收藏  举报