Luogu 线性基练习题

1. luogu P3857 [TJOI2008]彩灯

题意
n n 盏灯, m m 个开关( n , m 50 n,m⩽50 ),每个开关可以控制的灯用一串 O X OX 串表示, O O 表示可以控制(即按一下,灯的状态改变), X X 表示不可以控制,问有多少种灯的亮暗状态。

注: 开始时所有彩灯都是不亮的状态。

思路
线性基,线性基有一个性质,插入的数的任意一个集合的异或值都不同,所以若插入了 k k 个数,答案就是 2 k 2^k

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long 
using namespace std;

const int Max = 63;
char str[Max];
ll p[Max],s[Max];
ll n,m,cnt;
void insert_lb(ll x){
	for(int j = Max - 1; j >= 0; j--){
		if(x & (1ll << j))
		if(!p[j]){
			p[j] = x;
			break;
		}else{
			x ^= p[j];
		}
	} 
}
int main(){
	scanf("%lld %lld",&n,&m);
	while(m--){
		scanf("%s",str);
		ll x = 0;
		for(int i = 0; i < n; i++){
			if(str[i] == 'O'){
				x += (1ll << (n - 1 - i));
			}
		}
		insert_lb(x);
	}
	for(int i = 0 ; i < Max; i++){
		if(p[i]) cnt++;
	}
	printf("%lld",(1ll<<cnt) % 2008);
	return 0;
} 
 

2. luogu P4301 [CQOI2013]新Nim游戏

题意

两个参与者在各自的第一回合都能拿若干个整堆的火柴,可不拿但不能全部拿走,从第二回合开始规则和Nim游戏一样。求 先手是否能必胜,必胜时先手在第一回合拿的最少的火柴数。

思路

首先我们知道Nim游戏的一个结论:在Nim的游戏中若石子数异或和不为0则先手必胜。

所以先手要赢 就要在第一回合拿足够的石子,使得剩下的石子的异或和不为 0。

这个时候一条线性基的优美性质就出来了:线性基的任意异或和都为不为 0 。

所以我们只需要把线性基留下,剩下的在第一回合拿走就可以了

对于拿最少的火柴的问题,使用贪心策略,我们只需要在插入线性基的时候从大到小插入,即留下的石子都是相对的大 ,那么我们拿走的就是最少的。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long 
using namespace std;

const int Max = 1e4 + 7;
const int Maxbit = 63;
ll n,sum = 0;
int a[Max];
struct LinearBase{
	ll p[Maxbit];
	bool insert(ll x){
		for(int j = Maxbit - 1; j >= 0; j--){
			if(x & (1ll << j)){
				if(!p[j]){
					p[j] = x;
					return true;
				}else
					x ^= p[j];
			}
		}
		return false;
	}
}lb; 

bool cmp (const int &a,const int &b){
	return a > b;
}
int main(){
	scanf("%lld",&n);
	for(int i = 1; i <= n; i++)	 scanf("%d",&a[i]); 
	//sort(a+1,a+n+1,greater<int>());
	sort(a+1,a+1+n,cmp);
	for(int i = 1; i <= n; i++) if(!lb.insert(a[i])) sum += a[i];
	printf("%lld\n",sum); 
	return 0;
}

3. luogu P4570 [BJWC2011]元素

题意

N N 种矿石,每个矿石有序号和魔法值,序号异或和为 0 0 的矿石相互抵消,问序号异或和不为 0 0 最大的魔法值是多少。

思路
还是使用线性基的优美性质:线性基的任意异或和都为不为 0 。
先贪心把所有的矿石按魔法值从大到小排序,求出线性基,把线性基对应的魔法值求和,就是答案。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;

const int N = 1e3 + 7;
const int Maxbit = 63;
ll p[Maxbit];
ll ans = 0,n;

struct Mine {
	ll num;
	ll magic;
	bool operator < (const Mine &b) const {
		return magic > b.magic;
	}
} A[N];

void insert_lb() {
	for(int i = 1; i <= n; i++) {
		for(int j = Maxbit - 1; j >= 0; j--) {
			if(A[i].num & (1ll << j)) {
				if(!p[j]) {
					p[j] = A[i].num;
					ans += A[i].magic;
					break;
				} else {
					A[i].num ^= p[j];
				}
			}
		}
		
	}
}
int main() {
	scanf("%d",&n);
	for(int i = 1; i <= n; i++)
		scanf("%lld %lld",&A[i].num,&A[i].magic);
	sort(A + 1,A + 1 + n);
	insert_lb();
	printf("%lld",ans);
	return 0;
}

4. luogu P4869 albus就是要第一个出场

题意

给一个长度为n的序列,将其子集的异或值排序得到B数组,给定一个数字Q,保证Q在B中出现过,询问Q在B中第一次出现的下标。

思路

关于第 k 小的异或和问题已经在HDU 3949 XOR中提到过了,只不过 3949中的异或和是去重的,本题中的是不去重的。

那么我们要找出答案,就要解决两个问题:

  • Q前面有多少个不重的异或和?
  • Q前面每个不重的异或和都出现过多少次?

对于第一个问题:
我们可以将 3949 思路反过来。
步骤如下:

  1. 求出线性基后线性基内所有元素从小到大排序,并且只保留最高位

  2. 之后将Q二进制拆分,初始化ans=0,如果Q的第i位为1,就看线性基中是否存在一个数x满足x = (1<<i),如果存在,那么ans += 1<< i; 最后的ans就是Q在除去 0 的不重异或和的次序,也就是Q前面不重异或和的个数

对于第二个问题:
首先直接给出结论:

n 线 k , n 2 n 2 k , 2 n k . 这若 n 个数的异或线性基有 k 个, 则在 n 个数构成的集合的所有 2^n 个子集的异或和中共有 2^k 种值, 每种有 2^{n−k} 个.

下面来简单的证明一下:

线 k 线 ( ) , n k 线 由于异或线性基中的 k 个数线性无关(无法互相表出), 剩余 n−k 个未被插入线性基中的数能表出

2 n k 线 2 k , 2 n k 0 . 2^{n−k} 个值必定都能被线性基中的 2^k 个数表出, 于是就可以构造出 2^{n−k} 个不同的异或和为 0 的子集.

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;

const int N = 1e5 + 7;
const int Maxbit = 32;
int a,p[Maxbit],s[Maxbit],cnt = 0,q,n;
// 插入线性基 
void insert_lb(int x) {
	for(int i = Maxbit - 1; i >= 0; i--) {
		if(x & (1 << i)) {
			if(!p[i]) {
				p[i] = x;
				break;
			} else {
				x ^= p[i];
			}
		}
	}
}
//找出每个线性基的最高位  
void solve() {
	for(int i = 0; i < Maxbit; i++)
		if(p[i]) s[cnt++] = i;
}
ll qpow(int a,int b,int mod) {
	ll res = 1;
	while(b) {
		if(b & 1) {
			res = res * a % mod;
		}
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
int main() {
	scanf("%d",&n);
	for(int i = 1 ; i <= n; i++) {
		scanf("%d",&a);
		insert_lb(a);
	}
	
	solve(); 
	scanf("%d",&q);
	//每个异或和都出现 2^(n-cnt)次 
	ll tmp = qpow(2,n - cnt,10086),ans= 0;
	//找出比 Q 小的异或和个数 
	for(int j = cnt - 1; j >= 0; j--) {
		if(q & (1 << s[j])) {
			ans += (1 << j);
		}
	}
	ans = tmp * ans + 1;
	printf("%d\n",ans % 10086) ;
	return 0;
}

5.luogu P4151 [WC2011]最大XOR和路径

题意

给你一张n个点,m条边的无向图,每条边都有一个权值,求:1到n的路径权值异或和的最大值。

思路

任意一条路径都能够由一条简单路径(任意一条),在接上若干个环构成(如果不与这条简单路径相连就走过去再走回来)。

那么在对这些环进行分类:

1、直接与简单路径相连

相交的重复部分不算就可以了。
在这里插入图片描述假设路径A比路径B优秀一些,而我们最开始选择了路径B。显然,A与B共同构成了一个环。如果我们发现路径A要优秀一些,那么我们用B异或上这个大环,就会得到我们想要的A!

2、不与简单路径相连

我们需要跑过去,再跑回来对吧,这样的话,不管我们是怎么跑的,非环的路径对答案的贡献始终为0,图中的 k 路径 一来一回就抵消掉了,异或本身就是 0 。
在这里插入图片描述
所以综上所述,我们只需要找出所有环,把环上的异或和扔进线性基,随便找一条链,以它作为初值求最大异或和就可以了。

那么环的异或值怎么求呢?
我们再dfs的过程中,记录下到达当前点的异或和,并保存下来,存在一个pval[]的数组里。
现在假设,我们从橙色的点进入环中,

  • 然后dfs下一步走绿点,pval[绿] = pval[橙] ^ p1
  • 从绿色dfs到黄色,pval[黄] = pval[绿] ^ p2
  • 从黄色dfs到粉色,pval[粉] = pval[黄] ^ p3
  • 从粉色dfs到橙色,这时候橙色已经遍历过了,也就是环已经找到了,pval[粉] ^ p4 ^ pval[橙] 就是环上的异或和,pval[粉] ^ p4是从点 1开始到 粉点的异或和, 再异或上pval[橙]就是消除了从点 1开始到橙点的异或和,剩下的自然是环的异或和。

在这里插入图片描述

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long 
using namespace std;

const int N = 2e5 + 7;
const int Maxbit = 63;
int ver[N],nex[N],head[N];
ll edge[N],pval[N];
ll p[Maxbit];
int n,m,tot = 0,cnt = 0;
bool vis[N] = {0};

void addedge(int u,int v,ll w){
	nex[++tot] = head[u];
	ver[tot] = v;
	edge[tot] = w;
	head[u] = tot; 
	//printf("head[] = %d",head[u]); 
}
//插入线性基
void insert_lb(ll x){
	for(int i = Maxbit - 1; i >= 0; i--){
		if(x & (1ll << i)){
			if(!p[i]){
				p[i] = x;
				break;
			}else{
				x ^= p[i];
			} 
		}
	} 
}
//dfs找环 ,并记录异或和
void dfs(int cur,ll sum){
	//printf("%d->",cur);
	pval[cur] = sum;
	vis[cur] = true;
	for(int i = head[cur]; i; i = nex[i]){
		if(!vis[ver[i]]){
			//printf("v = %d\n",ver[i]);
			dfs(ver[i],sum ^ edge[i]);
		}else{
			insert_lb(sum ^ edge[i] ^ pval[ver[i]]);
		}	
	} 
}
//求最大异或和
ll max_xor(ll x){
	ll res = x;
	for(int i = Maxbit - 1; i >= 0; i--){
		if((res ^ p[i] ) > res){//注意 ^ 和 > 的运算先后 
			res ^= p[i];
		}
		//printf("res = %lld\n",res); 
	}
	return res;
}
int main(){
	int u,v;
	ll w;
	scanf("%d %d",&n,&m);
	while(m--){
		scanf("%d %d %lld",&u,&v,&w);
		addedge(u,v,w);
		addedge(v,u,w);
	}
	dfs(1,0);
	printf("%lld\n",max_xor(pval[n]));
	return 0;
} 
/*
5 7
1 2 2
1 3 2
2 4 1
2 5 1
4 5 3
5 3 4
4 3 2
*/

一部分结论和图都是从 An_Account 大佬那里参考 来的。

发布了141 篇原创文章 · 获赞 71 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/sinat_40872274/article/details/102211995