CF1360H Binary Median

原题链接  https://www.luogu.com.cn/problem/CF1360H

 

 

题目大意 

 

题解 

① 最常见最暴力的做法:

将读入的二进制转化为十进制,然后依次删除,删除过程中维护中位数;

虽然思路简单,但是考虑到实际维护的过程中十分复杂,对代码能力要求较高;

② 稍微巧妙一点的做法:

其实我们并不需要维护每个过程中的中位数是几,我们关心的只是全部删除后的中位数是几;

而且题目中给出了数据的原始个数(2m)和删除个数(n),也就是说,我们能知道删除完后的这 2m - n 的中位数是第几个数(设是第 k 个数,根据规律可得 k = ( 2m - n + 1 ) / 2),那么删除完后我们就输出这个位置的数就好了;

这就有了另一个思路:我们只需要在删除过程中维护哪个数变成了第 k 个数 。

拿样例模拟一下:

n = 3,m = 3

删除的数为:(010)2 = 2,(001)2 = 1 ,(111)2 = 7

删除前的所有数:

 

通过计算可以知道 k = ( 23 - 3 + 1 ) / 2 = 3,那么我们就确定了删完 3 个数之后的数据中第 3 个数是中位数;  

然后我们就死盯着第 3 个数不放,看哪个数在删除之后移到了第 3 个数的位置(红色的数字代表最后的中位数上的数);

 

由于数据是从 0 开始的,所以第 k 个数的大小为 k-1 (令 mid = k-1)。

删除 2:

 

删除 1:

 

删除 7:

 

过程中我们可以发现一个规律:

对于我们所要维护的中位数 mid 来说,如果删除的数比 mid 大,那么 mid 的值是不变的(参考上图删除 7 的过程);如果删除的数小于等于 mid,那么 mid 的值就变成了右边的那个数(以下称为“ 右移一次 ”),注意要跳过已经被删除了的数;

这么看来,我们可以先将所需删除的数从大到小排序,对于其中的 cnt 个比 mid 大的数,删除了并不影响 mid 的值;对于其中的 n - cnt 个小于等于 mid 的数,每删除一个 mid 就要右移一次,总共需要右移 n - cnt 次;

其实我们也可以只存比 mid 大的那些数,对于那些比 mid 小的数,我们只关心它有多少个;同时这样的话,我们也不必将所删除的数从大到小排序了,因为二分查找也可以从小到大排序啊qwq 。 

Code:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,n,m,cnt;
long long len,mid;
long long delet[1500];
bool read()              //读入单个数字 
{
    char ch=getchar();
    while(ch!='0'&&ch!='1') ch=getchar();
    return ch^48;
}
int check(long long x)   //查找x是否在delet数组里 
{
    return delet[lower_bound(delet+1,delet+1+cnt,x)-delet]==x;  //lower_bound偷懒qwq 
}
void work()
{
    for(long long i=mid+1;i<=len;i++)
    {
        if(!check(i))    //判断i是否被删除 
        {
            mid=i;       //没有被删除就来当mid 
            return ;
        }
    }
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&n,&m);
        len=1ll<<m;                  //len是初始数的个数 
        mid=(len-n+1)/2-1;           //删除一些数之后的中位数的位置上的数现在是几 
        memset(delet,0,sizeof(delet));
        cnt=0;                       //记录删除的数中有多少个数比mid大 
        for(int i=1;i<=n;i++)        
        {
            long long a=len/2;   
            long long num=0;         //num是转化为十进制之后的数 
            for(int j=1;j<=m;j++)    //二进制转化为十进制 
            {
                int x=read();
                if(x) num+=a;
                a/=2;
            }
            if(num>mid) delet[++cnt]=num;   //只需记录所有比mid大的数 
        }
        sort(delet+1,delet+1+cnt);   //从小到大排序,便于二分查找 
        for(int i=1;i<=n-cnt;i++)    //总共有n-cnt个数小于等于mid,就要右移n-cnt次 
            work();
        len/=2;    
        while(len)                   //十进制转化为二进制 
        {
            if(mid>=len) 
            {
                printf("1");
                mid-=len;
            }
            else printf("0");
            len/=2;
        }
        printf("\n");
    }
    return 0;
}

特别鸣谢 一扶苏一 帮我 debug;

 

 

posted @ 2020-06-11 19:04  暗い之殇  阅读(251)  评论(0编辑  收藏  举报