1. luogu P3857 [TJOI2008]彩燈
題意
有 盞燈, 個開關(),每個開關可以控制的燈用一串 串表示, 表示可以控制(即按一下,燈的狀態改變), 表示不可以控制,問有多少種燈的亮暗狀態。
注: 開始時所有彩燈都是不亮的狀態。
思路
線性基,線性基有一個性質,插入的數的任意一個集合的異或值都不同,所以若插入了個數,答案就是
#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]元素
題意
種礦石,每個礦石有序號和魔法值,序號異或和爲 的礦石相互抵消,問序號異或和不爲 最大的魔法值是多少。
思路
還是使用線性基的優美性質:線性基的任意異或和都爲不爲 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 思路反過來。
步驟如下:
-
求出線性基後線性基內所有元素從小到大排序,並且只保留最高位
-
之後將Q二進制拆分,初始化ans=0,如果Q的第i位爲1,就看線性基中是否存在一個數x滿足x = (1<<i),如果存在,那麼ans += 1<< i; 最後的ans就是Q在除去 0 的不重異或和的次序,也就是Q前面不重異或和的個數
對於第二個問題:
首先直接給出結論:
下面來簡單的證明一下:
#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 大佬那裏參考抄 來的。