前置技能点:线性基,异或
如果你不知道上面的东西,请先行了解
start_of_题面
Description
Input
第一行包含两个整数N和 M, 表示该无向图中点的数目与边的数目。 接下来M 行描述 M 条边,每行三个整数Si,Ti ,Di,表示 Si 与Ti之间存在 一条权值为 Di的无向边。 图中可能有重边或自环。
Output
仅包含一个整数,表示最大的XOR和(十进制结果),注意输出后加换行回车。
Sample Input
5 7
1 2 2
1 3 2
2 4 1
2 5 1
4 5 3
5 3 4
4 3 2
Sample Output
6
Hint
end_of_题面
假设我们已经完全清楚了线性基和异或的基本性质和作用
这道题要我们求出从1到n的路径使异或和最大,我们可以发现,如果图是这样的,那么绿色的部分是没有用的(请不要在意我的灵魂画图)
因为我们走过去还一定要走回来,而一条边权异或两次就相当于没有贡献
但是我们可以得出另一个结论,那就是如果有一个环,那么我们可以获得环上的权值,比如下图
也就是说,虽然我们无法获得绿色部分的权值,但是我们可以获得蓝色部分的权值。那么对于两个环,我们能够获得的权值是它们的异或和。首先,如果两个环不相交,这是显然的,因为我们要的就是路径的异或和,如果两个环相交,那么如下图
将两个环的异或和异或起来,会获得黑色部分的权值,这与实际情况是相符的,因为我们必须在两个环上都走一圈,这样的话中间红色的部分会走两次。
由于我们必须走到n,所以我们还有找到一条1到n的路径,所以我们发现我们的答案会是几个环和一条1到n的路径
如果路径和几个环不相交,这个图可能长这样
我们获得了红色的环上的路径和黑色的路径上的权值的异或和,而绿色的路径走了两次所以没有贡献
可能你会有疑问,为什么绿色的路径一定要走到一个环再走回来呢,事实上,如果我们直接从一个环走到另一个环再走回去,那么就又形成了一个环,那么它们就不再属于被走两次的路径,如下图
蓝色的部分又形成了一个环,那么它的答案应该被统计在环里,可以按照上面的分析计算
当然,也有可能我们走到黑色路径的一半再走出去,或者这个环本身就跟黑色的路径相交。我们并不在意,因为我们只需要知道答案而不需要知道具体是怎么走的。
现在我们的问题就是如何找到从1到n的路径了,事实上,我们随便找一条就可以。
那么我们就得到了一个算法:随便找一条1到n的路径,记异或和为 x o r xor xor,然后把所有的环的异或和放到线性基里,贪心地使 x o r xor xor异或线性基中的值是 x o r xor xor变大(具体怎么做参考线性基的操作),最后得到的值就是答案。
证明如下:
分为两种情况,第一种,我们选择的路径是一条最优的从1到n的路径,这样的话我们只需要考虑环上的权值,根据我们上面的分析,我们只需要把环上的值丢到线性基里就大功告成了。
第二种,我们选择的路径不是一条最优的从1到n的路径,这样说明至少还有一条从1到n的路径存在,那么我们的路径和最优的路径会组成一个环,如图
既然成了环,那么我们自然也会统计这个环,显然如果让我们的路径和最优的路径异或,我们的路径会被走两次,而最优的路径会被走一次,这样就变成了我们走了最优的路径,因为我们原来的路径没有了贡献,剩下的就是考虑其他的环了
对于处理环,我们只需要对图进行DFS,那么所有的非树边都会成环,处理一下就可以了。
对于复杂度,我们的复杂度是DFS的复杂度+环的数量*log(边权最大值),显然可以接受
至此我们就解决了这道题目
start_of_code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int N = 50000 + 5000;
const int M = 100000 + 1000;
int n, m, x, y, tot = 0;
int F[N], v[M << 1], nex[M << 1], EID = 1;
LL ans, basis[70], w[M << 1], Xor[N], wp, loop[M << 1];
bool vis[N];
inline LL read()
{
LL a = 0;
LL f = 1;
char ch;
while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
if(ch == '-')
f = -1;
else
{
a = a * 10;
a += ch - '0';
}
while((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))
{
a = a * 10;
a += ch - '0';
}
return a * f;
}
inline void add(int f, int t, LL z)
{
nex[EID] = F[f];
v[EID] = t;
w[EID] = z;
F[f] = EID++;
}
inline void dfs(int x)
{
vis[x] = 1;
for(int i = F[x];i;i = nex[i])
{
int t = v[i];
if(vis[t])
{
++tot;
loop[tot] = Xor[x] ^ Xor[t] ^ w[i];
continue;
}
Xor[t] = Xor[x] ^ w[i];
dfs(t);
}
}
inline void solve()
{
for(int i = 1;i <= tot;++i)
{
for(int j = 62;j >= 0;--j)
{
if(!((loop[i] >> j) & 1))
continue;
if(basis[j] == 0)
{
basis[j] = loop[i];
break;
}
loop[i] ^= basis[j];
}
}
ans = Xor[n];
for(int i = 62;i >= 0;--i)
if((ans ^ basis[i]) > ans)
ans ^= basis[i];
printf("%lld\n", ans);
}
int main()
{
n = read(), m = read();
for(int i = 1;i <= m;++i)
{
x = read(), y = read(), wp = read();
add(x, y, wp);
add(y, x, wp);
}
dfs(1);
solve();
return 0;
}