题目来源:Codeforces Round #558 (Div. 2) CF1163E Magical Permutation
算法:线性基
题目大意
给定一个,含有(n)个互不相同正整数的集合(S)。
我们称一个排列是合法的,当且仅当同时满足如下两个条件:
- 这是一个(0,1,dots ,2^x-1)的排列,其中(x)是一个非负整数。
- 排列里,任意相邻两数的异或值,都出现在(S)中。
请求出,最长的合法排列。换句话说,请你找到一个最大的(x),并构造出一个长度为(2^x)的合法排列。
数据范围:(1leq nleq 2 imes 10^5),(S)集合里的数(leq2 imes 10^5)。
本题题解
设(S)里最大的数为(M)。
如果排列的二进制下最高位,超过了(S)里最大的数(即(2^{x-1}>M)),那么这样的排列一定是不合法的。因为最高位一定会出现在异或值中,但它无法被(S)里的数表示出来。因此,满足条件的(x)不超过(log_2M+1)。于是我们考虑枚举所有(x),判断它是否可行。
引理:一个(x)可行当且仅当,(0,1,dots,2^x-1)都能被表示为(S)的一个子集的异或和。
证明-必要性:
考虑一个合法的排列,(p_0,p_1,dots ,p_{2^x-1}),设(p_t=0)。
首先,(p_t)就是一个空集的异或和,所以对(p_t)显然满足条件。
因为排列合法,所以在(p_{t+1},p_{t+2},dots ,p_{2^x-1})中,每个数和它前面一个数的异或值,都属于(S)。具体来说,设(c_i=p_{i-1}operatorname{xor}p_{i}),则(forall i>t:c_iin S),因此(p_{t+1},p_{t+2},dots ,p_{2^x-1})都能被表示为一段连续的(c_i)的异或和,也就是(S)里若干个数的异或和。
类似地,在(p_{t-1},p_{t-2},dots,p_0)中,每个数和它后面一个数的异或值,都属于(S)。设(d_i=p_{i}operatorname{xor}p_{i+1}),则(forall i<t:d_iin S),因此(p_{t-1},p_{t-2},dots,p_0)都能被表示为一段连续的(d_i)的异或和,也就是(S)里若干个数的异或和。
由此就证明了:【(0,1,dots,2^x-1)都能被表示为(S)的一个子集的异或和】是一个长度为(2^x)的排列合法的必要条件。枚举(x)后,我们可以通过对(S)建线性基,看线性基前(x)位是否填满,来检查(x)是否满足这个必要条件。
接下来要证明充分性,相当于把这个排列构造出来。也正是题目要我们做的事情。
现在我们知道,(0,1,dots,2^x-1)都能被表示为(S)的一个子集的异或和。根据线性基的理论,对(S)建线性基后,(S)的每个子集,都对应线性基的一个子集,它们异或和相等。所以(0,1,dots,2^x-1)每个数,都能被表示为:【(S)里,用来建线性基的,这(x)个数】的一个子集。并且每个数对应的子集互不相同:用来建线性基的有(x)个数,正好形成(2^x)个子集,和每个值一一对应。
通过线性基,我们能求出,每个值对应了哪个子集。前面说了,这些子集互不相同。于是把它们用二进制表示为(0,1,dots,2^x-1)。
那么问题转化为,把(0,1,dots,2^x-1)(所有子集)重新排列,使得任意相邻两个数,只有一位二进制为不同。这就是格雷码的定义,可以采用「CSP-S 2019」格雷码这题题面里介绍的构造方法。
时间复杂度(O(nlog n+nlog M+M))。
参考代码:
//problem:
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
const int MAXN=2e5,LOG=18;
int n;
uint s[MAXN+5];
uint mask_to_val[MAXN+5];
struct LinerBase{
int size;
uint base[MAXN+5],mask[MAXN+5],val[MAXN+5];
void insert(const uint& v){
uint t=v;
uint ma=0;
for(int i=LOG;i>=0;--i) if((t>>i)&1) {
if(!base[i]){
base[i]=t;
++size;
ma|=(1<<(size-1));
mask[i]=ma;
val[size]=v;
return;
}
else{
t^=base[i];
ma^=mask[i];
}
}
}
uint query_mask(const uint& v){
uint t=v;
uint ma=0;
for(int i=LOG;i>=0;--i) if((t>>i)&1) {
assert(base[i]);
t^=base[i];
ma^=mask[i];
}
return ma;
}
}B;
vector<uint> get_gray(uint x){
if(x==0){
vector<uint> res(1,0);
return res;
}
vector<uint> res=get_gray(x-1);
uint len=(1u<<(x-1));
for(uint i=0;i<len;++i){
res.push_back(res[len-1-i] | (1u<<(x-1)));
}
return res;
}
int main() {
cin>>n;
for(int i=1;i<=n;++i){
cin>>s[i];
}
sort(s+1,s+n+1);
int j=0;
int x=-1;
for(int i=0;i<=LOG;++i){
while(j+1<=n && s[j+1]<(1u << i)){
++j;
B.insert(s[j]);
}
if(B.size==i) x=i;
}
assert(x!=-1);
for(uint i=0;i<(1u<<x);++i){
mask_to_val[B.query_mask(i)]=i;
}
// cerr<<x<<endl;
// for(uint i=0;i<(1u<<x);++i)
// cerr<<mask_to_val[i]<<" ";
// cerr<<endl;
vector<uint> v=get_gray(x);
cout<<x<<endl;
for(uint i=0;i<(1u<<x);++i){
cout<<mask_to_val[v[i]]<<" ";
}
cout<<endl;
return 0;
}