文章目录
- [Lisa and the Martians](https://codeforces.com/contest/1851/problem/F)
- 问题建模
- 问题分析
- 1.分析给定的运算操作
- 2.方法1使用Trie树来查找最符合的数
- 代码
- 方法2通过性质,排序后找运算的最大值
- 代码
- 证明
Lisa and the Martians
问题建模
给定n个数和k,每个数小于 2 k 2^k 2k,问哪两个数分别与一个小于 2 k 2^k 2k的数x进行异或运算后再进行与运算的值最大,输出这两个数的编号和x。
问题分析
1.分析给定的运算操作
我们由外向内进行分析,运算最外层为两个数进行与运算,只有两个数对应位都为1才能使最终结果的对应位为1。接着考虑更里面一层中,异或上怎么样的x才能使两个数对应位都为1。若两个数相同,则异或的x对应位为0或者1即可让得到的两个数对应位都为1,若不同则无论x的对应位为什么值,都无法使得最终得到的两个数对应位同时为1。则我们需要找两个数其高位到低位的对应位尽可能相同,才能使得最终的运算结果尽可能大。
2.方法1使用Trie树来查找最符合的数
使用01-Trie对于输入的每个数,在已输入数中查找与该数高位尽可能相同的数,然后计算对应的结果,将该数放入Trie中,最终输出最大值对应使用数值的编号和x。
代码
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N = 2e5 + 10, M = N << 6, Mod = 998244353;
int a[N];
int son[M][2],idx;
int cnt[M];
int n,k;
///将x的编号插入到01-Trie中
void insert(int x, int pos) {
int p = 0;
///由高位向低位检索
for (int i = k - 1; ~i; i--) {
int &s = son[p][(x >> i) & 1];
if (!s) s = ++idx;
p = s;
}
cnt[p]=pos;
}
///检索与x高位尽可能相同的值的编号
int search(int x) {
int p = 0;
for (int i = k - 1; ~i; i--) {
int s =(x>>i)&1;
if (son[p][s]) {
p = son[p][s];
} else p = son[p][!s];
}
return cnt[p];
}
void solve() {
cin >> n >> k;
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
///初始化01-Trie的起始编号和各个结点
idx = 0;
///0是起始结点,此外每个数有k位,总共有n个数,每个数的每一位为1个结点,则总共有n*k+1个结点
for (int i = 0; i <= n * k; i++) {
son[i][0] = son[i][1] =0;
}
int ai = -1, aj = -1, ax = -1, ans = -1;
for (int i = 0; i < n; i++) {
if (i) {///第一个数插入后,再进行查询操作,否则会查询到未被初始化的索引
int j = search(a[i]);
///计算当前两个元素a[i]和a[j]进行运算后的最终结果
///两个元素对应位置若相同则通过异或变成0,然后取反,再将其限制在k位内,就是最终结果
int nans = (~(a[i] ^ a[j])) & ((1 << k) - 1);
if (nans > ans) {
ans = nans;
///ax为当前两个元素进行运算所需的x
///结果位最终若为1,两个数对应位一定相同,则其异或值x对应位一定与两个数对应位相反,则通过异或运算结果位于其中一个数可得到x该位对应的值,再将其限制在k位内,就是最终的x
ai = i, aj = j, ax =(nans^a[i])&((1<<k)-1);
}
}
insert(a[i], i);
}
cout <<aj+1 << " " << ai+1 << " " << ax << "\n";
}
int main() {
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
方法2通过性质,排序后找运算的最大值
因为我们要找高位到低位尽可能相同的两个数,这样运算结果最大,则这样的两个数若做异或运算其结果为最小值。则问题变成了找到两个做异或运算最小的数。而做异或运算最小的两个数,必定是相邻的,因为相邻的数其高位到低位相同的比较多则异或后高位到低位为0的也多,则可以排序所有元素后,计算相邻两个数的异或值找最小的即可。(该性质证明放在最后面。)
代码
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N = 2e5 + 10, M = N << 6, Mod = 998244353;
PII p[N];
bool cmp(PII &p1, PII &p2) {
return p1.x < p2.x;
}
void solve() {
int n,k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
scanf("%d", &p[i].x);
p[i].y=i;
}
sort(p+1,p+1+n,cmp);
int pos=-1,ans=((1<<k)-1);///初始时k位上都为1
for(int i=2;i<=n;i++){
int nans=(p[i].x^p[i-1].x);
if(nans<=ans) pos=i,ans=nans;
}
int ai=p[pos-1].x,aj=p[pos].x;
int res= (~(ai ^ aj)) & ((1 << k) - 1);///使用方法1中使用的计算方式计算x
cout <<p[pos-1].y<<" " <<p[pos].y<<" "<<((res^ai)&((1<<k)-1)) <<"\n";
}
int main() {
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
证明
证明:min( x ⨁ y x\bigoplus y x⨁y, y ⨁ z y\bigoplus z y⨁z )< x ⨁ z x\bigoplus z x⨁z,当x,y,z都为正整数且(x<y<z)
假设最高位到第i+1位,x,y,z都相同,z的第i位为1,x的第i位为0
- 若y第i位为1,则有 y ⨁ z y\bigoplus z y⨁z< x ⨁ z x\bigoplus z x⨁z
- 若y第i位为0,则有 x ⨁ y x\bigoplus y x⨁y< x ⨁ z x\bigoplus z x⨁z
所证完毕,则通过该证明,可以说明一个数异或值最小的数为异或上其相邻的数。