codeforce 618 div2 C题解+收获(位操作)

round #618 div2 C(文末有技巧)

这是题目链接->链接

题目

原题目是全英文的,所以我就粗略来翻译一下:

有这样一个函数f:f(x,y)=(x|y)-y;(其中|是按位或操作)

比如说:f(11,6)=(11|6)-6=15-6=9;(简单明了)

现在有一个数组 [a1,a2,…,an],让你求f(f(…f(f(a1,a2),a3),…an−1),an) ,数组中可能有相同的数字,让你对这个数组重新排序,以保证最终的结果是最大。(如果有多个答案,输出任意一种即可)

样例输入输出

输入
第一行输入一个n(1≤n≤10^5).
第二行输入n个数,a1,a2,…,an (0≤ai≤10^9).

输出
输出n个数,最后结果是最大的数组顺序,如果有多个答案,任意输出一种。

样例输入
4
4 0 11 6
样例输出
11 6 4 0

样例输入
1
13
样例输出
13

思路

我就不说我的思路了(萌新思路),官方题解+正规解答是这个样子的:

首先观察题目函数f(11,6)=11|6-6,既然是关于位操作的,那么来一波二进制运算:

  11:1011
    6:0110
ans:1001
  ~6:1001

最后经过大佬的慧眼(不是我),得出结论为
f(x|y)=(x|y)-y=x&(~y)!!!

ps:
~为取反操作,1变0,0变1,如0110(6),1001(~6)
&为按位与操作,有0则0,无0则1,如1011(11)&1001(~6)=1001

所以[a1,a2,…,an]就变成了a1&(~a2)&(~a3)…&(~an);

根据官方题解来讲a1对答案贡献最大(我觉得a1最为特殊),找到a1,其他的依次输出就可以了,找不到就原序输出;

那么问题来了,如何找到那个扛把子a1?

据大佬口述,只需要找到最高位的1在谁那,谁就是扛把子

那么思路油然而生▶️:

从最高位二进制最高位遍历每个数,一旦找到一个,就是a1,把它首先输出,再把其它的数依次输出,如果找不到,就原序输出:

#include <iostream>
#include <cstdio>
#define N 100005
using namespace std;
//@date: 2020-02-10 11:28:12
//@state:Yes
//@purpose:bitmasking(位屏蔽)

inline void read(int &x){
    //快速读取int
    int ch = getchar(); x = 0;
    bool f = false;
    while((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if(ch == '-'){f = true; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0'; ch = getchar();}
    if(f) x = -x;
}

int a[N],c[N];

int main()
{
    int n;
    read(n);
    for(register int i=1;i<=n;i++)
        read(a[i]);
    for(register int j=30;j>=0;j--)
    {
        int cnt=0,p;
        for(register int i=1;i<=n;i++)
            if((a[i]>>j)&1)//把a[i]右移j位就是取a[i]的第j+1位二进制数,看是否是1}
                cnt++,p=i;
        if(cnt==1)
        {
            cout<<a[p]<<" ";//输出扛把子a1
            for(register int k=1;k<=n;k++)
            {//输出其他的
                if(k!=p)
                    cout<<a[k]<<" ";
            }
            return 0;
        }
    }
    //没找到扛把子的情况,就原序输出
    for(register int i=1;i<=n;i++)
        cout<<a[i]<<" ";
    return 0;
}

小技巧(拿小本本记下来)

  1. 快速读取数字,实测比最快的scanf还要快!
inline void read(int &x){
    int ch = getchar(); x = 0;
    bool f = false;
    while((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if(ch == '-'){f = true; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0'; ch = getchar();}
    if(f) x = -x;
}
  1. 二进制的遍历

有人可能会问,为什么遍历二进制数j要从30开始,到0呢?
且听我细细道来~
首先int类型(指有符号整型),现在都是4字节32位了,所以范围就是-2^32 -1到2^32-1,也就是说最长为31位,剩下一位表示正负,我们不用管。
我们想要的就是从二进制最高位去遍历
而右移>>符号的意思正好满足我们获取最高位的功能
比如说:1011(11)
11>>3=1011(1),现在右移3位得到的就是第4位二进制数。

  1. bitmasking(位屏蔽)

位屏蔽的含义是从包含多个位集的一个或一组字节中选出指定的一(些)位。为了检查一个字节中的某些位,可以让这个字节和屏蔽字(bit mask)进行按位与操作(C的按位与运算符为&)——屏蔽字中与要检查的位对应的位全部为1,而其余的位(被屏蔽的位)全部为0。

以上是来自百度回答,这个名词百度百科中没有,但是我觉得这个回答很好。
但是你们是不是看的头疼啦(是的,啥也没看懂-.-),简而言之,取出二进制数的特定位来操作。
放在这道题上就是取出a[i]的第j+1位二进制数判断是否是1,用代码来说就是这个地方:

if((a[i]>>j)&1)//把a[i]右移j位就是取a[i]的第j+1位二进制数,看是否是1}
  1. 使用频繁的变量放在寄存器

register int的register表示使用cpu内部寄存器(寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件)的变量,而平时的int是把变量放在内存中,存到寄存器中可以加快变量的读写速度)

for(register int i=1;i<=n;i++)
        read(a[i]);

现在有人说C++最新标准已经不认这个了,反正我自己使用做对比仍有提升(现在时间2020.2.10),在多重循环中颇有奇效!!!嘘~这是我从这次比赛榜一的代码中学到的,自己知道就行了✔️

发布了67 篇原创文章 · 获赞 42 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_26235879/article/details/105357818