[WC2011]【bzoj2115】Xor —— 线性基

前置技能点:线性基,异或
如果你不知道上面的东西,请先行了解

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;
}

end_of_code

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值