题意:你被给予了n个数组a1,a2,...,an,每个数组有m个整数,我们把第x个数组的第y个元素记为axy。你可以选择两个数组,即使它们是同一个数组,这样你会得到一个由m个整数组成的新数组b,对于每个整数来说,都是两个数组每一个数的最大值。
你的目标是选择两个数组使得组合成的数组中最小的数是所有选择中最大的。
分析:最小值最大,我们可以采用二分搜索,我们二分[0,1e9]这个区间,假定答案在一个给定的位置,答案右边的区间是无法找到两个数组组合成一个b数组,使得这个数组的所有数都大于等于这个数(即作为最小值),如果我们枚举n * m个数列的组合,显然这样的复杂度太大,我们可以换个角度,采用二进制压缩,我们将大于等于查询点的值赋为1,小于1的数赋为0,那样,所有出现过的数列都不会超过1<<m种组合,因为m的范围为(1<=m<=8),那么所有出现过的数列最多有255种,那么我们枚举n个数列,然后将出现过的数列更新一下,没出现过的为-1,-1在计算机中的存储为原码形式,即32个1,然后,我们可以再枚举所有组合,255 * 255种组合,它们不能等于-1,如果等于-1,就意味着不存在给出的数列,然后,它们异或起来要等于(1 << m) - 1,即m个1,即这个异或出来的数组每个数都是大于等于检查点的,所以这个给出的两个数列是符合答案的。
启发:对于二分查找,我们可以采用二进制状态压缩,将大于等于和小于检查点的数划分成两类。
有道题和这道题很相似,通信线路,这道题将大于检查点的权值变成1,小于权值的点变成0,然后用二分查找答案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 300005;
const int M = 10;
int a[N][M];
int n, m;
int a1, a2;
bool check(int mid)
{
//1 << m个数为-1,即为32个1
vector<int> msk(1 << m, -1);
for (int i = 0; i < n; ++i)
{
int cur = 0;
for (int j = 0; j < m; ++j)
if (a[i][j] >= mid)
cur ^= (1 << j);
msk[cur] = i;
}
if (msk[(1 << m) - 1] != -1)
{
a1 = a2 = msk[(1 << m) - 1];
return true;
}
for (int i = 0; i < (1 << m); ++i)
{
for (int j = 0; j < (1 << m); ++j)
{
if (msk[i] != -1 && msk[j] != -1 && (i | j) == (1 << m) - 1)
{
a1 = msk[i];
a2 = msk[j];
return true;
}
}
}
return false;
}
int main()
{
//n个数列,每个数列m个整数
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
scanf("%d", &a[i][j]);
int l = 0, r = 1e9;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid))
l = mid;
else
r = mid - 1;
}
printf("%d %d
", a1 + 1, a2 + 1);
return 0;
}