【题解】CF1288D Minimax Problem
少判了一种情况,被 $\texttt{Hack}$ 掉了,心情复杂
题目链接:CF1288D 【Minimax Problem】
题意比较奇怪,要你求一个什么东西的最大值的最小值的最大值来着,建议先把题目仔细看几遍再往下看。
首先观察数据范围,发现 $m$ 特别小,最大只有 $8$ 来者,考虑在 $m$ 上搞点事情。
考虑 二分答案 套路
首先二分一个数字 $ans$ ,表示当前的答案。
然后把原本矩阵中大于等于 $ans$ 的位置设为 $1$ ,小于 $ans$ 的位置设为 $0$。
比如样例:
6 5
5 0 3 1 2
1 8 9 1 3
1 2 3 4 5
9 1 0 3 7
2 3 0 6 3
6 4 1 7 0
当 ans = 5 时,可以得到矩阵
1 0 0 0 0
0 1 1 0 0
0 0 0 0 1
1 0 0 0 1
0 0 0 1 0
1 0 0 1 0
又根据 $m$ 的范围,不难发现我们可以把每一行都压成二进制的形式。
这样就变成了 $n$ 个数字。
回到二分答案,我们现在的 $ans$ 是二分出来的,要怎么 $check$ 才行?
我们发现,只要当两个数字 或起来 正好等于 $2^m - 1$ 也就是二进制下的 $111...$ 就说明可行。
为什么?
在做或运算的时候,只要两个数字对应位置上有一个 $1$,当前位置就是 $1$。
这正好满足我们取 $max$ 的操作,如果两个都是 $1$ 呢?其实我们也不需要在意这两个 $1$ 原来数字的相对关系,因为他们显然都大于等于 $ans$。
那么现在问题就变成了:
给定 $n$ 个数字 $a_i$,判断是否存在 $i$,$j$ (可以相等)满足:$a_i$ $or$ $a_j== 2^m - 1$
发现我们可以把所有数都丢到一个桶里面,每次枚举到一个数,找一下这个数字哪些位置上为 $0$,那么你需要找的数字在这一位上一定是 $1$,其他位置上 $0$,$1$ 随意。
$DFS$ 即可。
注意一个小小的优化,如果一个数字的二进制为 $111...$,那么说明根本不需要找另一个数字,直接选两次自己即可。
还要注意一种情况
如果最后你根本没有选出答案,那么说明答案只能为 $0$,则任意选择两个行即可。就是这里送掉了我的 D !!!
代码也还比较好写。
//Code By CXY
#include<bits/stdc++.h>
using namespace std;
#define min _______min
#define max _______max
const int MAXN = 3e5 + 10;
const int INF = 1e9;
int n,m,ans1,ans2;
int a[MAXN][10],p[MAXN][10],c[MAXN],rev[MAXN];
int l = INF,r = -1,mid;
int buk[510];
inline int min(int x,int y) {return (x < y) ? x : y;}
inline int max(int x,int y) {return (x > y) ? x : y;}
bool find(int x,int p) {
bool res = false;
if(p < 0) {
if(buk[x]) ans2 = buk[x];//保存答案
return buk[x];
}
if(x & (1 << p)) return find(x,p - 1);
res |= find(x,p - 1);
res |= find(x + (1 << p),p - 1);
return res;
} //DFS 判断
inline bool chk() {
memset(buk,0,sizeof buk);
for(register int i = 1;i <= n; ++i) {
int now = 0;
for(register int j = 1;j <= m; ++j) {
now <<= 1;
if(a[i][j] >= mid) now++;
}//now就是这一行代表的数字
buk[now] = i;
c[i] = now;
if(c[i] == (1 << m) - 1) {ans1 = ans2 = i; return true;}//特殊小优化的情况
rev[i] = ((1 << m) - 1) ^ c[i];//必须为1的位置组成的数
}
for(register int i = 1;i <= n; ++i)
if(find(rev[i],7)) {
ans1 = i;//保存答案
return true;
}
return false;
}
int main () {
scanf("%d%d",&n,&m);
for(register int i = 1;i <= n; ++i)
for(register int j = 1;j <= m; ++j) {
scanf("%d",&a[i][j]);
l = min(l,a[i][j]);
r = max(r,a[i][j]);
}
while(l < r) {
mid = (l + r + 1) >> 1;
if(chk()) l = mid;
else r = mid - 1;
}//二分
if(ans1 == 0 && ans2 == 0) printf("1 1 \n");//!!!注意这个小情况
else printf("%d %d",min(ans1,ans2),max(ans1,ans2));
return 0;
}
我的 $D$ 啊啊啊本来是上分好场的来着呜呜呜
初三的 $OIer$ ,请多关照