Luogu 線性基練習題

1. luogu P3857 [TJOI2008]彩燈

題意
nn 盞燈,mm 個開關(n,m50n,m⩽50),每個開關可以控制的燈用一串 OXOX 串表示,OO 表示可以控制(即按一下,燈的狀態改變),XX 表示不可以控制,問有多少種燈的亮暗狀態。

注: 開始時所有彩燈都是不亮的狀態。

思路
線性基,線性基有一個性質,插入的數的任意一個集合的異或值都不同,所以若插入了kk個數,答案就是2k2^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]元素

題意

NN種礦石,每個礦石有序號和魔法值,序號異或和爲 00 的礦石相互抵消,問序號異或和不爲 00 最大的魔法值是多少。

思路
還是使用線性基的優美性質:線性基的任意異或和都爲不爲 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前面不重異或和的個數

對於第二個問題:
首先直接給出結論:

nk,n2n2k,2nk.這若 n 個數的異或線性基有 k 個, 則在 n 個數構成的集合的所有 2^n 個子集的異或和中共有 2^k 種值, 每種有 2^{n−k} 個.

下面來簡單的證明一下:

k(),nk由於異或線性基中的 k 個數線性無關(無法互相表出), 剩餘 n−k 個未被插入線性基中的數能表出

2nk2k,2nk0.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 大佬那裏參考 來的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章