2021icpc沈阳 Bitwise Exclusive-OR Sequence(染色)

本文介绍了一道关于异或方程求解的问题,参赛者需要解决一个包含异或运算的方程组,找到变量的最小和。通过分析,可以构建一个图,并对图进行染色来确定最小的1的个数。文章详细阐述了如何通过高斯消元法的思考误区,转向图论和染色算法来解决问题,并提供了相关的C++代码实现。同时,提到了优化技巧,如减少数据类型和在检测到矛盾时立即退出程序。
部署运行你感兴趣的模型镜像

题目链接:B-Bitwise Exclusive-OR Sequence_第 46 届 ICPC 国际大学生程序设计竞赛亚洲区域赛(沈阳) (nowcoder.com)

题意:给你一个n以及m,n代表变量的个数,m代表限制方程的个数,接下来给出m行,每行3个数a,b,c代表第a个变量和第b个变量的异或值为c,问n个变量的最小和是多少,如果异或方程出现矛盾,则直接输出-1。

今天刚打的沈阳站,我当时一看是关于异或方程求解问题,我就以为是高斯消元解异或方程组,还猜了一个结论,就是所有的自由元都为0时结果最小,都把代码打完了才发现数组都开不下(-_-)

下面来说一下这道题的正确思路吧:由于w<2^30,所以我们可以考虑他的每一个位,对于a^b=c,如果c的二进制下第k位为1,则a和b的二进制下的第k位必定一个为0,一个为1,总之就是不能相同,如果c的二进制下第k位为0,则a和b的二进制下的第k位要么全为0,要么全为1,总之就是相同。利用这个限制条件我们可以建一个图来存储第k位的情况,如果a和b的二进制下的第k位不同,我们就可以在他们之间连一个权值为1的边,如果a和b的二进制下的第k位相同,我们就可以在他们之间连一个权值为0的边。对其他限制条件做出相同处理,我们就可以形成一张图,然后我们就可以对这张图进行染色,染色要求每个点只能染1或者0,图中1的个数就是我们所有的变量中第k位为1的数的个数,我们只需要求出每张图中1的最小个数再乘以这一位上的权值就好,为什么1的个数还有最小值而不是一个定值呢?因为如果我们对一张图已经染好了颜色,那我们交换0和1发现这张图仍然满足之前的性质,所以我们只需要对这两种情况取一个最小值就好。但是需要注意的是每一位上的图不一定是一张连通图,而有可能是由好几个连通图组成的,我们对每一个连通图求出最小的1的个数然后相加即可。

这道题好像卡常,建议大家能用int的地方就不要用longlong,而且当发现条件中有矛盾时我们可以直接在被调用函数中直接输出-1并退出程序。用的方法是exit(0);

下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e5+10,M=2e5+10;
int c[30][N],h[30][N],ne[30][M*2],e[30][M*2],w[30][M*2],idx[M*2];//注意建的是双向边
ll po[30],n,m;
int cnt;
bool flag=true;
bool vis[30][N];
void add(int p,int x,int y,int z)
{
	e[p][idx[p]]=y;
	w[p][idx[p]]=z;
	ne[p][idx[p]]=h[p][x];
	h[p][x]=idx[p]++;
}
int dfs(int p,int u,int color)
{
	c[p][u]=color;//给当前点染色 
	int ans=color;//ans记录连通块中染的1的个数 
	for(int i=h[p][u];i!=-1;i=ne[p][i])
	{
		int j=e[p][i];
		if(!vis[p][j])
		{
			cnt++;
			vis[p][j]=true;
			ans+=dfs(p,j,color^w[p][i]);//子节点染的颜色为color^w[p][i]
		}
		else if(c[p][j]!=color^w[p][i])
		{
			cout<<-1<<endl;
			exit(0);//直接退出整个程序,在调用函数里面也可以使用,比较好用 
		}
	}
	return ans;
}
ll solve(int p)
{
	ll ans=0;//记录第p张图中染色最少需要的1的个数 
	for(int i=1;i<=n;i++)
	{
		if(vis[p][i]) continue;//整张图可能有多个连通块 
		vis[p][i]=true;
		cnt=1;//cnt为整个连通块中点的个数
		int t=dfs(p,i,1);//若将连通块中第一个点染成1,则t为整个连通块中1的个数 
		ans+=min(t,cnt-t);//cnt-t为若将连通块中第一个点染成0,则t为整个连通块中1的个数 
	}
	return ans;
}
int main()
{
	po[0]=1;
	for(int i=1;i<30;i++) po[i]=po[i-1]*2;
	cin>>n>>m;
	//不要忘记初始化 
	for(int i=0;i<30;i++)
	for(int j=0;j<=n;j++)
		h[i][j]=-1;
	while(m--)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		for(int i=0;i<30;i++)//对每个位建立一个图 
		{
			if(c>>i&1)
			{
				add(i,a,b,1);
				add(i,b,a,1);
			}
			else//0边也必须要建
			{
				add(i,a,b,0);
				add(i,b,a,0);
			}
		}
	}
	ll ans=0;
	for(int i=0;i<30;i++)
		ans+=po[i]*solve(i);//加上最小的1的个数乘以其权值 
	//由于当答案不存在时直接在调用函数中结束程序,所以这个地方的ans一定是正确结果 
	printf("%lld\n",ans);
	return 0;
}

您可能感兴趣的与本文相关的镜像

Seed-Coder-8B-Base

Seed-Coder-8B-Base

文本生成
Seed-Coder

Seed-Coder是一个功能强大、透明、参数高效的 8B 级开源代码模型系列,包括基础变体、指导变体和推理变体,由字节团队开源

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值