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 大佬那里参考抄 来的。