洛谷P4151 [WC2011]最大XOR和路径(线性基/结论)

题目描述

XOR(异或)是一种二元逻辑运算,其运算结果当且仅当两个输入的布尔值不相等时才为真,否则为假。 XOR 运算的真值表如下(11 表示真, 00 表示假):

img

而两个非负整数的 XOR 是指将它们表示成二进制数,再在对应的二进制位进行 XOR 运算。

譬如 1212 XOR 99 的计算过程如下:

img

故 1212 XOR 9=59=5。

容易验证, XOR 运算满足交换律与结合律,故计算若干个数的 XOR 时,不同的计算顺序不会对运算结果造成影响。从而,可以定义 KK 个非负整数 A1A1,A2A2,……,AK−1A**K−1,AKA**K的 XOR 和为

A1A1 XOR A2A2 XOR …… XOR AK−1A**K−1 XOR AKA**K

考虑一个边权为非负整数的无向连通图,节点编号为 11 到 NN,试求出一条从 11 号节点到 NN 号节点的路径,使得路径上经过的边的权值的 XOR 和最大。

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

输入格式

输入文件 xor.in 的第一行包含两个整数 NN 和 MM, 表示该无向图中点的数目与边的数目。

接下来 MM 行描述 MM 条边,每行三个整数 SiS**i, TiT**i , DiD**i, 表示 SiS**i 与 TiT**i 之间存在一条权值为 DiD**i 的无向边。

图中可能有重边或自环。

输出格式

输出文件 xor.out 仅包含一个整数,表示最大的 XOR 和(十进制结果)。

输入输出样例

输入 #1复制

5 7
1 2 2
1 3 2
2 4 1
2 5 1
4 5 3
5 3 4
4 3 2

输出 #1复制

6

看到异或和最大自然想到线性基,关键在于路径选取。注意到:路径可以重复经过某些点或边,当一条边在路径中出现了多次时,其权值在计算 XOR 和时也要被计算相应多的次数。这意味着走回头路对于答案是没有贡献的。不妨考虑整个图为树的情况,这样1到N的路径的最大异或和显然为1到N的树上最短路径的异或和。这时候再考虑有环的情况,设当前选择的1到N的链路径为L,同时有环C1通过路径L1与L相连,那么就可以用C1来增广路径异或和(L1走两边对于异或和的贡献为0)。此时就可以找到所有的环,将其周长插入线性基,求线性基与链L的最大异或和。至于链L怎么确定其实无所谓,因为多条1到N的链实际上也会构成环。而至于遍历边的顺序不同导致找到的环不同实际上也不会影响最终的答案,证明可以看https://www.luogu.com.cn/problem/solution/P4151的题解(猜想我们的算法找到的简单环能够通过异或操作得到所有的简单环。)

#include <bits/stdc++.h>
#define N 200005
#define M 400005
#define int long long
using namespace std;
int head[N], ver[2 * M], edge[2 * M], Next[2 * M];
int n, m, tot = 0;
vector<int> B;
void insert(int x) {
    for(auto b : B)
        x = min(x, b ^ x);
    for(auto &b : B)
        b = min(b, b ^ x);
    if(x)
        B.push_back(x);
}
void add(int x, int y, int z) {
	ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
bool vis[N];
int dis[N];
int query(int x) {
	int ans = x;
	for(int i = 0; i < B.size(); i++) {
		if((ans ^ B[i]) > ans) {
			ans ^= B[i];
		}
	}
	return ans;
}
void dfs(int x, int sum) {
	vis[x] = 1;
	dis[x] = sum;
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i], z = edge[i];
		if(vis[y]) {
			insert(sum ^ z ^ dis[y]);
		} else {
			dfs(y, sum ^ z);
		}
	}
}
signed main() {
	cin >> n >> m;
	memset(vis, 0, sizeof(vis));
	for(int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w);
		add(v, u, w);
	}
	dfs(1, 0);
	cout << query(dis[n]);
	return 0;
}
原文地址:https://www.cnblogs.com/lipoicyclic/p/15019026.html