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

被折叠的 条评论
为什么被折叠?



