2021 ICPC沈阳 B.Bitwise Exclusive-OR Sequence(位运算+图论)

题目描述

题目链接

题目大意

有n个点和m个约束,每个约束包含三个数u,v,w,表示a[u]^a[v]=w.
求满足m个约束 并且 和最小的序列的和。

题目分析

这 很 明 显 是 一 个 图 论 问 题 , 对 于 每 个 约 束 u − v − w , 我 们 可 以 在 u 和 v 之 间 连 一 条 权 值 为 w 的 边 。 这很明显是一个图论问题,对于每个约束u-v-w,我们可以在u和v之间连一条权值为w的边。 uvwuvw

因 为 题 目 对 于 约 束 并 没 有 太 多 的 限 制 。 因 此 这 个 图 是 可 能 存 在 环 和 不 连 通 的 。 因为题目对于约束并没有太多的限制。因此这个图是可能存在环和不连通的。

首 先 , 对 于 图 的 不 连 通 , 我 们 可 以 分 别 处 理 每 个 连 通 块 。 并 将 每 一 块 的 权 值 进 行 累 加 。 首先,对于图的不连通,我们可以分别处理每个连通块。并将每一块的权值进行累加。

当 一 个 连 通 块 中 存 在 环 的 时 候 , 我 们 可 以 算 出 环 上 所 有 边 的 权 值 异 或 和 , 如 果 环 上 的 异 或 和 不 为 0 , 则 无 合 法 解 , 当一个连通块中存在环的时候,我们可以算出环上所有边的权值异或和,如果环上的异或和不为0,则无合法解, 0 输 出 − 1 。 因 为 环 上 所 有 边 的 异 或 和 是 把 环 上 所 有 点 都 用 了 两 遍 , 因 此 环 上 的 异 或 和 应 该 是 0 。 输出-1。因为环上所有边的异或和是把环上所有点都用了两遍,因此环上的异或和应该是0。 10

除 去 这 两 种 情 况 , 剩 下 的 就 只 有 链 了 。 最 后 我 们 来 看 一 下 如 何 处 理 链 。 除去这两种情况,剩下的就只有链了。最后我们来看一下如何处理链。
假 设 一 个 链 上 的 点 权 为 ( a 1 , a 2 , a 3 , … , a n ) 假设一个链上的点权为(a1,a2,a3,…,an) a1,a2,a3,,an

a 1 ∧ a 2 = w 1 a_1∧a_2=w_1 a1a2=w1
a 2 ∧ a 3 = w 2 a_2∧a_3=w_2 a2a3=w2
… … ……
a n − 1 ∧ a n = w n − 1 a_{n-1}∧a_n=w_{n-1} an1an=wn1

我 们 对 其 做 一 个 转 换 : 我们对其做一个转换:

a 1 ∧ a 2 = w 1 a_1∧a_2=w_1 a1a2=w1
a 1 ∧ a 3 = w 1 ∧ w 2 a_1∧a_3=w_1∧w_2 a1a3=w1w2
a 1 ∧ a 4 = w 1 ∧ w 2 ∧ w 3 a_1∧a_4=w_1∧w_2∧w_3 a1a4=w1w2w3
… … ……
a 1 ∧ a n = w 1 ∧ w 2 ∧ … ∧ w n − 1 a_1∧a_n=w_1∧w_2∧…∧w_{n-1} a1an=w1w2wn1

设 s i 为 w 1 − i 的 异 或 前 缀 和 , 我 们 再 移 一 下 项 设s_i为w_{1-i}的异或前缀和,我们再移一下项 siw1i

a 2 = s 1 ∧ a 1 a_2=s_1∧a_1 a2=s1a1
a 3 = s 2 ∧ a 1 a_3=s_2∧a_1 a3=s2a1
… … ……
a n = s n − 1 ∧ a 1 a_n=s_{n-1}∧a_1 an=sn1a1

因 此 , 我 们 只 需 要 确 定 第 一 个 数 a 1 , 即 可 算 出 所 有 的 其 它 的 点 的 点 权 。 因此,我们只需要确定第一个数a_1,即可算出所有的其它的点的点权。 a1
如 果 我 们 枚 举 a 1 的 所 有 可 能 值 , 那 一 定 是 会 超 时 的 。 所 以 我 们 需 要 寻 找 其 它 方 法 。 如果我们枚举a_1的所有可能值,那一定是会超时的。所以我们需要寻找其它方法。 a1

我 们 可 以 按 位 枚 举 每 一 个 s i , 并 记 录 每 一 位 1 的 数 量 c n t [ j ] 。 我们可以按位枚举每一个s_i,并记录每一位1的数量cnt[j]。 si1cnt[j]
对 于 每 一 位 j , 1 的 个 数 是 c n t [ j ] , 一 条 链 上 的 点 数 为 n 。 对于每一位j,1的个数是cnt[j],一条链上的点数为n。 j1cnt[j]n

如 果 c n t [ j ] > n / 2 , 说 明 有 一 半 以 上 的 s i 在 第 j 位 上 都 是 1 。 因 为 a i + 1 = s i ∧ a 1 , 所 以 只 要 让 a 1 在 第 j 位 如果cnt[j]>n/2,说明有一半以上的s_i在第j位上都是1。因为a_{i+1}=s_i∧a_1,所以只要让a_1在第j位 cnt[j]>n/2sij1ai+1=sia1a1j 也 为 1 , 就 能 让 一 半 以 上 的 点 权 减 少 ( 1 < < j ) , 而 剩 下 的 点 权 增 加 ( 1 < < j ) , 对 于 总 和 来 说 是 减 小 了 。 也为1,就能让一半以上的点权减少(1<<j),而剩下的点权增加(1<<j),对于总和来说是减小了。 1(1<<j)(1<<j)

用 这 种 方 式 就 能 在 O ( n l o g n ) 的 复 杂 度 内 , 求 出 一 条 链 的 和 最 小 值 。 用这种方式就能在O(nlogn)的复杂度内,求出一条链的和最小值。 O(nlogn)

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <algorithm>
#include <iomanip>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=1e5+5,mod=1e9+7;
vector<PII> h[N];
bool vis[N],flag=1;			//flag=0表示存在非法环,无解
LL f[N],cnt[35];			//f[v]表示从根节点到v点这条链上的异或和
vector<int> a;				//记录这条链上的点(除第一个节点外)
void dfs(int u)
{
	vis[u]=true;
	for(auto t:h[u])
	{
		int v=t.x,w=t.y;
		if(vis[v])			//如果v被访问过,说明存在环
		{
			if((f[u]^w)!=f[v]) flag=0;		//如果环上的异或和不为0,用flag记录
		}
		else {
			f[v]=f[u]^w;			//更新f[v]的值
			a.push_back(v);			//将v加入链中
			dfs(v);					//继续递归
		}
	}
}
LL get()
{
	memset(cnt,0,sizeof cnt);		//先清空cnt[]
	int x=0;					//表示链上的第一个节点的点权
	for(int i=0;i<30;i++)
	{
		for(int u:a)		//记录链上所有数第i位是否为1(f[u]其实和讲解里的s[]等价)
			if((f[u]>>i)&1) cnt[i]++;
		if(cnt[i]>a.size()/2) x+=(1<<i);
	}
	LL ans=x;
	for(int u:a) ans+=f[u]^x;		//记录链上的点权和
	return ans;
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int n,m;
	cin>>n>>m;
	while(m--)				//建图
	{
		int u,v,w;
		cin>>u>>v>>w;
		h[u].push_back({v,w});
		h[v].push_back({u,w}); 
	}
	LL ans=0;
	for(int i=1;i<=n;i++)			//遍历所有点
	{
		if(!vis[i]&&flag)			//如果点i没被访问过,且不存在非法环
		{
			a.clear(); 				//清空数组
			dfs(i);					//dfs求f[]
			ans+=get();				//计算当前链的最小和
		}
	}
	if(flag) cout<<ans<<endl;
	else cout<<-1<<endl;
	return 0; 
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值