校内NOI模拟赛006T1 签到sign 线性基
感想
这道题还算良心,有30pts的暴力……但是这次模拟赛又双叒叕是勒堕赛制,而且试题的标题居然还是NOI模拟测试!打暴力的时候我也没有想到我可以现学现卖一个线性基呢...
数据又是脚造的(
虽然标题时模拟赛,但是这里我主要是想讲线性基而不是这道题。
例题
【问题描述】
有一个𝑛 × 𝑚的网格,第𝑖行第𝑗列的格子我们记作(𝑖,𝑗)。每个格子上都有一个数字,(𝑖,𝑗)上的数字为𝑎𝑖,𝑗。 你现在在(1,1),你想走到(𝑛, 𝑚)处签到,每次你可以走到上下左右四个相邻的格子中的一个,当然,你不能走出网格。
你现在的心情值为𝑎1,1,你的心情飘忽不定,你每走一步,你的心情值就会异或上走到的格子上的数字。
你希望你签到的时候心情最好,为此你可以任意绕远路,甚至可以走到签到处的时候暂时不签到。
请你求出最大的心情值。
【输入格式】
第一行两个正整数𝑛, 𝑚。
接下来𝑛行,每行𝑚个非负整数,其中第𝑖行第𝑗个整数表示𝑎𝑖,𝑗。
【输出格式】
输出一个整数,表示答案。
【样例输入】
2 2
1 2
3 4
【样例输出】
7
【数据范围】
对于 30%的数据,𝑛, 𝑚 ≤ 4。
另有 30%的数据,𝑛, 𝑚 ≤ 100,𝑎𝑖,𝑗 ≤ 1000。
对于 100%的数据,𝑛, 𝑚 ≤ 500,𝑎𝑖,𝑗 ≤ 109。
分析
转化题意:
求一条路径(可以随便乱走,只规定起点和终点),使路径上所有数的xor max
看到这类xor问题,首先想到了线性基!线性基最适合解决这类xor问题了!
稍稍分析一下,结合xor的性质:
- ABB=A
- if A^B=C then A^C=B & B^C=A
- ...
发现,对于一个点如果我们走了两次,那么就相当于那个点没有被选
所以对于一个点可以任意决定它是否被选
但是又因为要从起点出发,终点停下,易得那么一共要走的步数的奇偶性与n+m-1相同
也就是说,需要保证从线性基中取出的xor max这个数一定要保证是被奇/偶数个数字xor得到的
那么怎么办呢?
answer:利用线性基的贪心,插入的时候把每个数的第30(不一定要30,只要够高就行)位赋为1,这样取出来的数字一定会保证是奇数个(如果是偶数个,230就会被xor消失了),再将贪心初值赋值为0(如果需要奇数个)或230(需要偶数个数的xor)
如果初值是0,那么从高位开始贪心时,2^30一定会被选择(为1),从而保证取出的数一定是奇数个
如果初值是230,那么从高位开始贪心时,230一定要是0,保证了取出的数一定是偶数个
最后输出的时候别忘记减去2^30
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#define re register
#define debug printf("Now is %d\n",__LINE__);
using namespace std;
int n,m,ans;
ULL a[600][600];
ULL p[63];
void insert(ULL x)
{
	for(int i=62;i>=0;i--)
	{
		if((x>>i)&1)
		{
			if(p[i]==0)
			{
				p[i]=x;
				break;
			}
			else
			{
				x^=p[i];
			}
		}
	} 
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
			insert(a[i][j]+(1<<30));
		}
	}
	ULL ans=((n+m-1)&1?(0):(1<<30));
	for(int i=62;i>=0;i--)
	{
		if((ans^p[i])>ans) ans^=p[i];
	}
	cout<<ans-(1<<30);
	return 0;
}
当然,你可能 还不知道线性基是什么……没事,我也刚刚才弄懂。
线性基
接下来详细的讲一下线性基是什么
定义
线性基是一种擅长处理异或问题的数据结构
有一个正整数集A,线性基P;
设值域为[1,N],就可以用一个长度为logN的数组来描述一个线性基。
特别地,线性基第i位上的数二进制下最高位也为第i位。
一个线性基满足,对于它所表示的所有数的集合A,A中任意多个数异或所得的结果均能表示为线性基中的元素互相异或的结果,即意,线性基能使用异或运算来表示原数集使用异或运算能表示的所有数。运用这个性质,我们可以极大地缩小异或操作所需的查询次数。
构造(插入)
一开始线性基为空
对于要插入的每一个数,从高位开始判断;
设当前为x的二进制的最高位i(这里是指最高为1的位)
- 若线性基第i位为0,则直接插入(赋值)并退出
- 否则x=x^p[i],重复以上操作直到x==0
void insert(ULL x)
{
	for(int i=62;i>=0;i--)
	{
		if((x>>i)&1)
		{
			if(p[i]==0)
			{
				p[i]=x;
				break;
			}
			else
			{
				x^=p[i];
			}
		}
	} 
}
判断
判断一个数x是否能被这个线性基表示出来
判断和插入类似
如果判断退出时,x==0,则说明此时线性基已经可以表示x了,否则说明为了插入x,线性基插入了一个新元素
bool judge(ULL x)
{
	for(int i=62;i>=0;i--)
	{
		if((x>>i)&1)
		{
			if(p[i]==0)
			{
				return 0;
			}
			else
			{
				x^=p[i];
			}
		}
	} 
	return 1;
}
查询最大值
基于贪心,我们从高到低地扫描线性基。直接比较取max即可。
ULL ans=0;
for(int i=62;i>=0;i--)
{
	if((ans^p[i])>ans) ans^=p[i];
}
显然每次ans不会变劣,所以最后的ans就是答案。
当然,还可以给ans的初值不为0
这样,查询的就是max(ans^线性基能表达的数)了
查询最小值
首先要特判是否能表达0
再根据线性基查询最大值时的贪心一样,从低位开始扫描即可。
当然这次不需要扫完,只要p[i]有数据,返回即可。因为从低位开始扫的话,越xor就会越大。
if(flag) return 0;
for(int i=0;i<=62;i++)
      if(p[i]) return p[i];

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号