题目大意
给出 n ( 2 ≤ n ≤ 1 e 5 ) n(2 \leq n \leq 1e5) n(2≤n≤1e5)个五元组 p i = { a i , b i , c i , d i , e i } p_i = \{a_i,b_i,c_i,d_i,e_i\} pi={ ai,bi,ci,di,ei},要从中选出2个五元组 i , j i, j i,j,最终的得分为 m i n { m a x ( a i , a j ) , m a x ( b i , b j ) , m a x ( c i , c j ) , m a x ( d i , d j ) , m a x ( e i , e j ) } min\{max(a_i, a_j), max(b_i, b_j),max(c_i, c_j),max(d_i, d_j), max(e_i, e_j)\} min{ max(ai,aj),max(bi,bj),max(ci,cj),max(di,dj),max(ei,ej)},问最多的得分是多少。
解题思路
典型的"最大的最小"型问题,考虑二分答案 x x x,本题的难点是如何写 c h e c k ( ) check() check()函数。
对于当前要判断的答案 x x x,先扫一遍,考虑用状压 s t a sta sta存储每个元素相对于 x x x的大小,每个五元组的每个元素分别和 x x x比较大小,若大于等于 x x x那么置为 1 1 1,否则置为 0 0 0。因此问题变成了如何找到两个不同的 s t a sta sta,使得 s t a i ∣ s t a j = 11111 sta_i | sta_j = 11111 stai∣staj=11111。状态的个数很少,又因为要找最小的下标,因此用 s e t set set存储每个状态的下标(实际上可以只存两个最小的),然后枚举状态 i , j i, j i,j,查看是否存在 s t a i ∣ s t a j = 11111 sta_i | sta_j = 11111 stai∣staj=11111。
一开始想到的是对于二分的答案,要在一个五元组中先找到一个恰好存在这个答案的五元组,然后再枚举。但实际上没必要这样,只要存在大于等于该答案的情况,那么最终的答案一定会更大,这是二分答案需要注意的细节。
因为没地方提交,我自己造了几组数据测试都没问题,时间复杂度为 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int Mod = 1e9 + 7;
const int maxn = 1e5 + 10;
struct node {
int a[5];
int id, sta;
} p[maxn];
set<int> s[35];
int n, ans, ans1, ans2;
bool flag;
void upd(int x, int y) {
if (!flag) {
flag = 1;
ans1 = x, ans2 = y;
} else {
if (ans1 > x)
ans1 = x, ans2 = y;
else if (ans1 == x)
ans2 = min(ans2, y);
}
}
bool check(int x) {
flag = 0;
for (int i = 0; i < 32; i++) s[i].clear();
for (int i = 1; i <= n; i++) {
p[i].sta = 0;
for (int j = 0, k = 1; j < 5; j++, k *= 2) {
if (p[i].a[j] >= x) p[i].sta += k;
}
s[p[i].sta].insert(p[i].id);
}
bool ok = 0;
for (int i = 0; i < 32; i++) {
if (s[i].empty()) continue;
for (int j = 0; j < 32; j++) {
if (s[j].empty() || (i == j && s[i].size() < 2)) continue;
if ((i | j) == 31) {
int a1, b1;
if (i == j)
a1 = *s[i].begin(), b1 = *next(s[i].begin());
else
a1 = *s[i].begin(), b1 = *s[j].begin();
ok = 1;
if (a1 > b1)
upd(b1, a1);
else
upd(a1, b1);
}
}
}
return ok;
}
bool duipai() {
//对拍程序
int res = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int cur = inf;
for (int k = 0; k < 5; k++) {
cur = min(cur, max(p[i].a[k], p[j].a[k]));
}
res = max(res, cur);
}
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int cur = inf;
for (int k = 0; k < 5; k++) {
cur = min(cur, max(p[i].a[k], p[j].a[k]));
}
if (res == cur) {
if (ans == res && ans1 == i && ans2 == j) return 1;
// cout << ans << " " << ans1 << " " << ans2 << ENDL;
// cout << res << " " << i << " " << j << ENDL;
return 0;
}
}
}
}
/*
3
8
1 3 6 1 7
5 3 5 6 1
5 0 0 7 7
0 4 1 2 4
4 7 6 7 2
3 4 4 4 7
3 1 5 3 1
6 6 3 5 7
4
5 4 3 10 1
8 6 7 3 10
9 5 3 10 10
2 6 6 8 5
6
4 8 9 1 7
1 9 4 3 4
4 7 9 6 7
2 9 4 4 6
9 9 10 10 4
7 2 5 9 5
*/
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 0; j < 5; j++) {
cin >> p[i].a[j];
p[i].id = i;
}
int l = 0, r = maxn;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else
r = mid - 1;
}
// if (!duipai())
// cout << "WA" << ENDL;
// else
// cout << "AC" << ENDL;
cout << ans << " " << ans1 << " " << ans2 << ENDL;
}
return 0;
}