【Educational Codeforces Round 80(div2)】D-Minimax Problem (二分+状态压缩)

一、题目链接

https://codeforces.com/contest/1288/problem/D

二、题意

给定n个数组 a 1 a_1 a1 a 2 a_2 a2,……, a n a_n an, 每个数组有m个整数。用 a x , y a_{x,y} ax,y 描述第x个数列的第y个数。

你可以选出任意数列 a i a_i ai a j a_j aj(1≤i,j≤n,i与j可以相等),从中得到同样有m个整数的新数列{ b m b_m bm},使得对于任意k∈[1,m] b k b_k bk = max( a i , k a_{i,k} ai,k, a j , k a_{j,k} aj,k)

对于一个数列{ b m b_m bm},存在最小值min。有你的目标是找出所有min的最大值。输出这个值所在的两行行序号。(从1开始)

三、数据范围

1 ≤ n ≤ 3 * 1 0 5 10^5 105

1 ≤ m ≤ 8

四、解题思路

一句话概括题意,找同列两个数最大值的同行最小值的最大值。

寻找“最小值的最大值”,可以想到二分查找。怎么判断当前mid是否为正确结果呢?

在某个新获取的数列{ b m b_m bm}中,存在最小值min。由题意,只需要确认 mid = min 在一个数列{ b m b_m bm}成立,就可以更新下界,进行下一步查找了。但仅仅枚举数列{ b m b_m bm}就是n*n*m,我们需要更优的算法。

数列{ b m b_m bm}的获取方式是比较某两个数列对应位置的数字,再加上m数据范围的特殊性,我们有了按位或的思路。

对于数列 a i a_i ai a j a_j aj,将其中≥min的数字换成1,<min的数字换成0,再进行按位或,必将得到一个全为1构成的数列P。

我们来看一下按位或的规则:

1|0=1

1|1=1

0|0=0

0|1=1

其实这个操作过程就是题目的“取大值”。数列P即为按上述规则修改后的数列b。

以样例第一行和第五行为例:

5 0 3 1 2

2 3 0 6 3

得到

5 3 3 6 3 → min=3

修改原数列:

5 0 3 1 2 → 1 0 1 0 0

2 3 0 6 3 → 0 1 0 1 1

按位或后 → 1 1 1 1 1

核心思路:二分查找时,对每一行,将>=mid的值改为1,<mid的数字改为0,一行转存为一个二进制数。得到的二进制数范围为[0, 2 m 2^m 2m) 。在这个范围进行按位或,若得到 2 m − 1 2^m-1 2m1,(含m个1的二进制数),则mid = min。这个mid保存为可能的答案。

五、AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int n, m, a[300005][10], ans, ans1, ans2, x[300];
//ans是题目寻找的最大值,ans1、ans2为要求输出的行序号 

bool check(int k)
{
    memset(x, 0, sizeof(x)); //记得初始化!!!
    for (int i = 0; i < n; i++)
    {
        int tot = 0; //tot为这一行转换01后的二进制数
        for (int j = 0; j < m; j++)
            if (a[i][j] >= k) tot += (1 << j); 
        		//else tot += (0 << j);
        x[tot] = i + 1; //x[num]记录该数列在第几行
    }
    for (int i = 0; i < (1 << m); i++)
        for (int j = i; j < (1 << m); j++)
        {
            if (x[i] && x[j] && (i | j) == (1 << m) - 1) 
            //x[i]和x[j]表示的二进制数都存在于输入中
            {
                ans1 = x[i];
                ans2 = x[j];
                return 1;
            }
        }
    return 0;
}
int main() {
    cin >> n >> m;
    int l = 1000000000, r = -1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
        {
            cin >> a[i][j];
            if (a[i][j] < l) l = a[i][j];
            if (a[i][j] > r) r = a[i][j];
        }
    ans = l;
    while(l <= r) //二分查找模板
    {
        int mid = (l + r) / 2;
        if (check(mid))
        {
            l = mid + 1;
            ans = mid;
        }
        else r = mid - 1;
    }
    cout << ans1 << ' ' << ans2;
    return 0;
}

呜呜呜,俺也是刚开始写题解,若有表述不清还请多多包涵QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值