Loj#3130「COCI 2018.12」Praktični【线性基】

正题

题目链接:https://loj.ac/p/3130


题目大意

给出\(n\)个点\(m\)条边的一张有权无向图,你每次可以选择一个边集异或上一个值,要求最少次数使得所有简单环异或和都为\(0\)

\(1\leq n,m\leq 10^5\)


解题思路

先找一棵生成树,然后每条非树边都会产生一个简单环,显然这些简单环合法了其他的也一定合法。

而肯定存在一种最优的方案是只改非树边,因为如果该树边首先我们可以一次改一个集合所有必须改一条树边会对多个简单环产生不同的影响,而如果我们异或的是\(val\),那么我们能做到的只是让某个简单环异或上\(val\)(奇数条树边操作),或者不异或上\(val\)(偶数条树边操作),所以是和我们操作非树边能做到的是相同的。

那么问题就变为已知一些数要异或多少,求最少操作次数了,我们用线性基求出最小的线性空间就好了。

时间复杂度:\(O(n+m\log m)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define mp(x,y) make_pair(x,y)
using namespace std;
const int N=1e5+10;
struct node{
	int to,next,w;
}a[N<<1];
int n,m,tot,k,w[N],d[N],ls[N],v[N];
bool vis[N];
void addl(int x,int y,int w){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;a[tot].w=w;
	return;
}
void dfs(int x,int fa){
	vis[x]=1;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa)continue;
		if(vis[y]){
			if(i&1)v[(i+1)/2]=w[x]^w[y]^a[i].w;
		}
		else{
			w[y]=w[x]^a[i].w;
			dfs(y,x);
		}
	}
	return;
}
void ins(int x){
	for(int i=29;i>=0;i--)
		if((x>>i)&1){
			if(d[i])x^=d[i];
			else {d[i]=x;k+=(x!=0);break;}
		}
	return;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y,w;i<=m;i++){
		scanf("%d%d%d",&x,&y,&w);
		addl(x,y,w);addl(y,x,w);
	}
	dfs(1,0);
	for(int i=1;i<=m;i++)ins(v[i]);
	printf("%d\n",k);
	for(int i=0;i<=29;i++)
		if(d[i]){
			int cnt=0;
			for(int j=1;j<=m;j++)
				cnt+=((v[j]>>i)&1);
			printf("%d %d ",d[i],cnt);
			for(int j=1;j<=m;j++)
				if((v[j]>>i)&1)
					printf("%d ",j);
			putchar('\n');
		}
	return 0;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/15563829.html