少判了一种情况,被 $\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$ ,请多关照

标签: 位运算, 搜索, 二分, 二分答案

添加新评论